Почему предопределенный элемент 1С теряет свой статус и как этого избежать?

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

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

В нашем случае, проблема возникла со стандартным справочником Контрагенты, у которого есть реквизит ЮридическоеФизическоеЛицо (составного типа). После создания предопределенного элемента КонтрагентП и его успешной работы, редактирование данных в связанном справочнике ЮридическиеЛица привело к тому, что КонтрагентП потерял свой статус. Давайте подробно разберем, почему это происходит, как исправить уже возникшую проблему и, самое главное, как предотвратить ее повторное появление на рабочих базах.

Мгновенное восстановление статуса предопределенного элемента

Первый шаг в решении проблемы, когда предопределенный элемент перестает быть таковым, — это понять механизм его идентификации. Предопределенный элемент в конфигураторе и соответствующий ему элемент в информационной базе (ИБ) связаны через специальный стандартный реквизит ИмяПредопределенныхДанных. Если этот реквизит в элементе ИБ становится пустым или в него записывается некорректное значение, элемент автоматически теряет свой предопределенный статус.

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

Для восстановления статуса предопределенности элемента нам необходимо программно установить правильное значение реквизита ИмяПредопределенныхДанных. Это можно сделать, получив объект элемента и присвоив ему соответствующее имя, указанное в конфигураторе.


// Сначала найдем элемент в справочнике. Это можно сделать по коду, наименованию или другим реквизитам.
// В данном примере, если элемент стал обычным, его уже нельзя получить через точку.
// Поэтому мы ищем его как обычный элемент справочника.
// Предположим, мы знаем код или наименование, которое было у предопределенного элемента.
// В нашем случае это был КонтрагентП, но как обычный элемент он мог иметь другое наименование/код,
// если пользователь его менял. Лучше искать по GUID, если он сохранился, или по другим уникальным полям.
// Если же предопределенный элемент исчез, а стал виден как "обычный" элемент, это значит,
// что его ссылка осталась прежней, просто сбросился признак.
// Давайте предположим, что мы знаем имя, которое было у предопределенного элемента в конфигураторе,
// например "КонтрагентП", и нам нужно найти соответствующий ему объект в базе.
// Мы можем пройтись по всем элементам и найти тот, у которого пустое ИмяПредопределенныхДанных,
// но его Ссылка совпадает с ранее известной ссылкой на предопределенный элемент.
// Либо, если мы хотим восстановить конкретный элемент, который визуально потерял значок,
// но мы знаем его ссылку или можем найти его по какому-либо другому уникальному признаку (например, по коду).

// Допустим, мы знаем внутренний код элемента, который потерял статус.
// В данном случае, это гипотетический КонтрагентП, который стал обычным.
// Его текущее имя в конфигураторе, которое мы хотим вернуть, это "КонтрагентП".
// Важно использовать точное имя, которое было задано в метаданных конфигурации для этого предопределенного элемента.

// Пример поиска элемента по коду:
ЭлементСправочника = Справочники.Контрагенты.НайтиПоКоду("000000001"); // Используем реальный код элемента
Если ЭлементСправочника = Неопределено Тогда
    // Если по коду не нашли, можно попробовать по наименованию или другим реквизитам
    ЭлементСправочника = Справочники.Контрагенты.НайтиПоНаименованию("Название КонтрагентаП");
КонецЕсли;

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

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

Выясняем и устраняем корневую причину потери статуса предопределенности

Хотя программное восстановление ИмяПредопределенныхДанных решает проблему здесь и сейчас, это лишь симптоматическое лечение. Наша главная задача — понять, почему это произошло, и предотвратить повторение в будущем. В нашем случае, ключевая подсказка заключалась в том, что проблема возникала после редактирования связанных данных в справочнике ЮридическиеЛица.

Мы выяснили, что в системе срабатывает механизм оповещения: при изменении данных в ЮридическиеЛица, вызывается процедура Оповестить. В справочнике Контрагенты, который имеет связь с ЮридическиеЛица, в свою очередь, обрабатывается событие через процедуру ОбработкаОповещения.

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

Таким образом, ЗаполнитьЗначенияСвойств непреднамеренно обнуляло реквизит ИмяПредопределенныхДанных, приводя к потере статуса предопределенности.

Как предотвратить нежелательную перезапись ИмяПредопределенныхДанных

Чтобы избежать такой ситуации, мы должны явно исключить реквизит ИмяПредопределенныхДанных из списка свойств, которые метод ЗаполнитьЗначенияСвойств будет копировать. Это делается путем передачи массива исключений в качестве третьего параметра.

Рассмотрим пример того, как это должно выглядеть в процедуре ОбработкаОповещения справочника Контрагенты (или в любом другом месте, где происходит подобное копирование свойств):


// Пример обработчика в модуле объекта справочника Контрагенты
Процедура ОбработкаОповещения(ИмяСобытия, Параметр, Источник)
    Если ИмяСобытия = "ИзменениеЮридическогоЛица" Тогда
        // Проверяем, что оповещение пришло от ЮридическогоЛица и содержит нужные данные
        Если ТипЗнч(Параметр) = Тип("СправочникСсылка.ЮридическиеЛица") ИЛИ 
           ТипЗнч(Параметр) = Тип("СправочникОбъект.ЮридическиеЛица") Тогда
            
            // Получаем объект текущего элемента Контрагента, который обрабатывает оповещение
            // (то есть, ЭтотОбъект - это Контрагент, который ссылается на измененное ЮридическоеЛицо)
            ОбъектКонтрагента = ЭтотОбъект; // В модуле объекта ЭтотОбъект уже является Объектом
            
            // Создаем массив исключений для реквизитов, которые не должны быть перезаписаны.
            // Крайне важно включить сюда "ИмяПредопределенныхДанных".
            МассивИсключений = Новый Массив();
            МассивИсключений.Добавить("ИмяПредопределенныхДанных");
            // Если есть другие реквизиты, которые специфичны для Контрагента
            // и не должны копироваться из ЮридическогоЛица, их также следует добавить:
            // МассивИсключений.Добавить("НекоторыйРеквизитКонтрагента");
            
            // Теперь вызываем ЗаполнитьЗначенияСвойств, передавая массив исключений.
            // Параметр должен быть объектом или структурой со свойствами, которые мы хотим скопировать.
            // В данном случае, если Параметр - это ссылка на ЮридическоеЛицо, возможно, 
            // нам потребуется получить его объект или структуру его свойств для корректного заполнения.
            // Или же Параметр уже содержит структуру обновленных данных.
            // Предположим, Параметр - это Ссылка на ЮридическоеЛицо, и мы хотим обновить реквизиты Контрагента
            // на основании данных этого ЮридическогоЛица.
            
            // Для примера, получим объект ЮридическогоЛица, если Параметр - это ссылка
            ИсточникДанных = Параметр;
            Если ТипЗнч(Параметр) = Тип("СправочникСсылка.ЮридическиеЛица") Тогда
                ИсточникДанных = Параметр.ПолучитьОбъект();
            КонецЕсли;

            // Заполняем свойства Контрагента из ИсточникДанных, исключая ИмяПредопределенныхДанных
            ЗаполнитьЗначенияСвойств(ОбъектКонтрагента, ИсточникДанных, МассивИсключений);
            
            // После изменения свойств необходимо записать объект
            Попытка
                ОбъектКонтрагента.Записать();
                // Сообщить("Контрагент " + ОбъектКонтрагента.Наименование + " успешно обновлен.");
            Исключение
                Сообщить("Ошибка при записи контрагента " + ОбъектКонтрагента.Наименование + ": " + ОписаниеОшибки());
            КонецПопытки;
        КонецЕсли;
    КонецЕсли;
КонецПроцедуры

Таким образом, добавление реквизита ИмяПредопределенныхДанных в МассивИсключений гарантирует, что это критически важное поле останется неизменным при автоматическом обновлении свойств, сохраняя статус предопределенного элемента.

Мы подчёркиваем: всегда, когда используете методы, которые могут массово перезаписывать свойства объекта (например, ЗаполнитьЗначенияСвойств, Заполнить и т.д.), и работаете с объектами, которые могут быть предопределенными, обязательно анализируйте, не затронет ли это критичные системные реквизиты, такие как ИмяПредопределенныхДанных. Проактивное исключение этих реквизитов из процесса копирования является лучшей практикой.

Дополнительные механизмы потери статуса предопределенности и особенности работы

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

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

  2. Обновление конфигурации базы данных: При обновлении конфигурации базы данных, если предопределенный элемент был удален из конфигурации (по сравнению с версией, хранящейся в базе данных), соответствующий объект в ИБ помечается на удаление, и у него автоматически снимается признак "Предопределенный". Это логичное поведение, но его важно учитывать.

  3. Ошибки обмена данными: В системах с распределенными информационными базами (РИБ) или при использовании других механизмов обмена данными, сбои или некорректная логика обмена могут привести к нарушению соответствия между предопределенными элементами конфигурации и элементами в ИБ. Например, при некорректной синхронизации мог быть создан дубликат, а оригинал потерял статус, или же поле ИмяПредопределенныхДанных было сброшено в процессе обмена.

  4. Программное изменение (общее): Любой программный код, который явно или неявно обнуляет реквизит ИмяПредопределенныхДанных для предопределенного элемента, приведет к потере его статуса. Это может быть результатом ошибок в кастомных обработках, загрузках данных или других модификациях.

Понимание этих механизмов помогает нам комплексно подходить к защите целостности предопределенных данных.

Как предотвратить потерю статуса предопределенности: Лучшие практики

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

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

  2. Использование свойства ОбновлениеПредопределенныхДанных: Начиная с платформы 8.3, у объектов конфигурации (например, у справочников) есть свойство ОбновлениеПредопределенныхДанных, которое управляет поведением платформы при создании и обновлении предопределенных элементов. Оно может принимать следующие значения:

    • Авто: Система самостоятельно определяет, нужно ли создавать или обновлять предопределенные элементы. Это режим по умолчанию и обычно достаточен для большинства случаев. Система пытается поддерживать актуальность предопределенных элементов согласно конфигурации.

    • ОбновлятьАвтоматически: Система активно создает и обновляет предопределенные элементы в информационной базе, если они отсутствуют или их данные не соответствуют конфигурации. Этот режим обеспечивает максимальную надежность для доступа к предопределенным элементам.

    • НеОбновлятьАвтоматически: Система не будет автоматически создавать и обновлять предопределенные элементы. В этом случае, при попытке обращения к отсутствующему предопределенному элементу будет сгенерировано исключение. Этот режим используется крайне редко и требует полного ручного контроля за наличием и состоянием предопределенных данных в ИБ. Его применение оправдано только в специфических сценариях, где автоматическое обновление нежелательно.

    В большинстве случаев рекомендуется использовать Авто или ОбновлятьАвтоматически, чтобы платформа сама заботилась о синхронизации предопределенных элементов конфигурации с базой данных.

  3. Использование предопределенных элементов по назначению: Мы создаем предопределенные элементы для надежного обращения к ним из программного кода по имени, а не по коду или наименованию, которые могут быть изменены пользователем. Это обеспечивает стабильность работы алгоритмов независимо от пользовательских переименований или перекодировок.

  4. Ограничения для подчиненных справочников: Мы должны помнить, что предопределенные элементы не могут быть созданы у подчиненных справочников. И наоборот, справочник, содержащий предопределенные элементы, не может быть сделан подчиненным. Это фундаментальное ограничение платформы.

  5. Программное управление предопределенностью (с платформы 8.3): С определенных версий платформы 8.3 появилась возможность программно связывать существующие элементы с предопределенными данными, присваивая им ИмяПредопределенныхДанных, или, наоборот, делать предопределенные элементы обычными, очищая этот реквизит. Это предоставляет нам дополнительную гибкость, но требует крайней осторожности.

    
    // Программно связать существующий элемент с предопределенным именем из конфигуратора
    // Этот метод используется редко и требует глубокого понимания последствий.
    // Чаще всего это делается для исправления ошибок или миграции.
    ЭлементОбъект.ИмяПредопределенныхДанных = "ИмяПредопределенногоЭлементаИзКонфигуратора";
    ЭлементОбъект.Записать();
            
    // Программно сделать предопределенный элемент обычным (очистить связь)
    // Также используется осторожно, когда предопределенный элемент больше не нужен
    // или должен стать обычным по какой-то бизнес-логике.
    ЭлементОбъект.ИмяПредопределенныхДанных = "";
    ЭлементОбъект.Записать();
    

    Мы используем эти возможности только в тех случаях, когда это абсолютно необходимо и полностью контролируется. Неосторожное применение может привести к непредсказуемым последствиям.

Анализ длительной реструктуризации и ошибок неуникальности кода

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

Длительность реструктуризации

Когда мы вносим изменения в метаданные (добавляем новый реквизит, изменяем тип существующего, добавляем индекс или, как в нашем случае, добавляем предопределенный элемент в справочник, что может потребовать обновления структуры данных или добавления служебных записей), платформа 1С:Предприятие выполняет процесс реструктуризации базы данных. Это не просто "обновление", а часто создание полностью новой таблицы в СУБД с измененной структурой, перенос всех данных из старой таблицы в новую, а затем удаление старой. Для больших таблиц, содержащих миллионы записей, этот процесс может занимать значительное время — от нескольких часов до нескольких дней в особо крупных базах.

В нашем случае, добавление нового предопределенного элемента в справочник Контрагенты, содержащий более миллиона элементов, вызвало столь длительную реструктуризацию. Хотя мы не добавляли новый реквизит или не меняли тип, добавление предопределенного элемента все равно требует внесения изменений в метаданные и потенциально в служебные таблицы для обеспечения его корректной работы и индексации. Эти изменения могут спровоцировать полный цикл реструктуризации для таблицы справочника.

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

Ошибки неуникальности кода

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

Ошибки неуникальности кода при реструктуризации могут возникать по нескольким причинам:

  1. Служебные записи и ранее удаленные элементы: При реструктуризации платформа может обнаруживать дубликаты кодов среди элементов, которые были помечены на удаление, но еще не удалены физически. Если конфигуратор позволяет создавать предопределенные элементы с кодами, которые могли быть использованы ранее, это может привести к коллизии.

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

  3. Ошибки в данных: Иногда проблемы с неуникальностью кода могут быть вызваны давними ошибками в самой базе данных, когда по каким-то причинам (сбои, неконтролируемые программные изменения, некорректный обмен) были созданы дубликаты кодов, которые проявляются только при попытке реструктуризации, требующей переиндексации или полной проверки уникальности.

  4. Временные таблицы при реструктуризации: В процессе создания новых таблиц и переноса данных, платформа может создавать временные структуры. Ошибки уникальности могут возникать в этих временных таблицах до того, как данные будут полностью перенесены в целевую структуру. Если новый предопределенный элемент, который мы добавляли, каким-то образом конфликтовал с существующими данными (например, по внутреннему идентификатору или служебному полю, а не по видимым коду/наименованию), это могло спровоцировать ошибку.

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

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

Для эффективной работы с предопределенными элементами и своевременного обнаружения проблем, мы используем ряд визуальных и программных средств:

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

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

  3. Функции БСП (Библиотеки стандартных подсистем): В составе БСП есть общие модули и функции, специально предназначенные для работы с предопределенными элементами. Мы можем использовать их для диагностики и контроля:

    • ПроверитьОтсутствующиеПредопределенныеЭлементы: Эта функция помогает выявить, есть ли в базе данных предопределенные элементы, которые должны быть согласно конфигурации, но по какой-то причине отсутствуют (были удалены или не созданы).

    • ПроверитьДублированиеПредопределенныхЭлементов: Эта функция позволяет обнаружить ситуации, когда в базе данных существует несколько элементов, претендующих на роль одного и того же предопределенного элемента (например, если по ошибке было создано несколько элементов с одинаковым ИмяПредопределенныхДанных, или один элемент потерял свойство, а другой был создан заново).

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

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

← На главную