Как программно открыть форму записи регистра сведений по ключу записи и избежать ошибки «Объект не найден»?

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

При разработке в среде «1С:Предприятие» перед программистом часто возникает задача программного открытия формы записи регистра сведений для редактирования конкретной строки. Стандартный подход подразумевает создание ключа записи и его передачу в параметры открываемой формы. Однако на практике разработчики нередко сталкиваются с неприятной ошибкой «Объект не найден». Особенно часто это происходит при переходе регистра из категории независимых непериодических в категорию периодических.

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

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

В чем причина ошибки «Объект не найден»?

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


Функция КлючЗаписиЗаметки(Пользователь) Экспорт 
    Возврат РегистрыСведений.ЗаметкиПользователя.СоздатьКлючЗаписи(Новый Структура("Пользователь", Пользователь)); 
КонецФункции

И затем на клиенте вызывает открытие формы:


Структура = Новый Структура("Ключ", ОбщегоНазначения.КлючЗаписиЗаметки(Данные.Пользователь)); 
ОткрытьФормуМодально("РегистрСведений.ЗаметкиПользователя.ФормаЗаписи", Структура);

Этот код отлично работал, пока регистр сведений был непериодическим. Но как только регистр сделали периодическим, система начала выдавать ошибку «Объект не найден».

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

Когда мы передаем в метод СоздатьКлючЗаписи структуру, содержащую только измерение Пользователь, платформа инициализирует поле Период значением по умолчанию (пустой датой 01.01.0001 00:00:00). Система пытается найти в базе данных запись с указанным пользователем и пустой датой. Естественно, такой записи в регистре нет, и платформа возвращает ошибку «Объект не найден».

Решение проблемы: формирование ключа с учетом Периода

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

Рассмотрим измененную функцию создания ключа записи на сервере:


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

Теперь при вызове этой функции на клиенте мы передаем в нее не только имя пользователя, но и конкретную дату (период), полученную, например, из среза последних или из запроса:


ПараметрыФормы = Новый Структура("Ключ", ОбщегоНазначения.КлючЗаписиЗаметки(Данные.Период, Данные.Пользователь)); 
ОткрытьФорму("РегистрСведений.ЗаметкиПользователя.ФормаЗаписи", ПараметрыФормы);

Примечание: вместо устаревшего и не рекомендуемого метода ОткрытьФормуМодально лучше использовать стандартный метод ОткрытьФорму.

Оптимизация: создание ключа записи на клиенте без обращения к серверу

Каждое обращение к серверу через вызов функций общих модулей снижает общую производительность интерфейса. В управляемом приложении 1С мы можем создавать объект типа РегистрСведенийКлючЗаписи непосредственно на клиенте (&НаКлиенте), полностью избегая серверных вызовов.

Для этого мы используем конструктор Новый с явным указанием типа ключа записи в виде строки. Параметром конструктора выступает массив, содержащий структуру с ключевыми полями.

Посмотрим на пример реализации этого подхода:


&НаКлиенте
Процедура ОткрытьЗаметкуНаКлиенте(ДанныеЗаметки)
    
    // Формируем структуру ключевых полей
    СтруктураКлючевыхПолей = Новый Структура;
    СтруктураКлючевыхПолей.Вставить("Период", ДанныеЗаметки.Период);
    СтруктураКлючевыхПолей.Вставить("Пользователь", ДанныеЗаметки.Пользователь);
    
    // Помещаем структуру в массив
    МассивКлюча = Новый Массив;
    МассивКлюча.Добавить(СтруктураКлючевыхПолей);
    
    // Создаем ключ записи прямо на клиенте
    КлючЗаписи = Новый("РегистрСведенийКлючЗаписи.ЗаметкиПользователя", МассивКлюча);
    
    // Открываем форму
    ПараметрыФормы = Новый Структура("Ключ", КлючЗаписи);
    ОткрытьФорму("РегистрСведений.ЗаметкиПользователя.ФормаЗаписи", ПараметрыФормы);
    
КонецПроцедуры

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

Обработка сценария, когда записи еще нет в базе данных (создание новой записи)

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

Чтобы сделать логику открытия универсальной, проанализируем ситуацию и применим следующий алгоритм:

  1. Проверяем на сервере физическое существование записи по ключевым полям.
  2. Если запись существует, открываем форму по ключу записи (для редактирования).
  3. Если записи нет, открываем форму, передавая значения полей через параметр ЗначенияЗаполнения. В этом случае откроется чистая форма новой записи, где переданные поля заполнятся автоматически.

Реализуем данный алгоритм на практике. Сначала напишем серверную функцию проверки существования записи:


&НаСервереБезКонтекста
Функция ЗаписьСуществует(Период, Пользователь)
    
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ Первые 1
        |	1 КАК Поле
        |ИЗ
        |	РегистрСведений.ЗаметкиПользователя КАК ЗаметкиПользователя
        |ГДЕ
        |	ЗаметкиПользователя.Период = &Период
        |	И ЗаметкиПользователя.Пользователь = &Пользователь";
        
    Запрос.УстановитьПараметр("Период", Период);
    Запрос.УстановитьПараметр("Пользователь", Пользователь);
    
    Возврат Не Запрос.Выполнить().Пустой();
    
КонецФункции

Теперь напишем клиентскую процедуру, которая будет гибко открывать форму:


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

Что делать, если регистр подчинен регистратору?

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

Если не передать в структуру ключа поле Регистратор, платформа снова вернет ошибку. При работе с подчиненными регистрами ключ записи формируется аналогично, но в структуру обязательно добавляются новые поля:


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

Альтернативный метод: Использование «ПоказатьЗначение»

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


&НаКлиенте
Процедура ОткрытьЧерезПоказатьЗначение(ДанныеЗаметки)
    
    МассивКлюча = Новый Массив;
    МассивКлюча.Добавить(Новый Структура("Период, Пользователь", ДанныеЗаметки.Период, ДанныеЗаметки.Пользователь));
    КлючЗаписи = Новый("РегистрСведенийКлючЗаписи.ЗаметкиПользователя", МассивКлюча);
    
    // Платформа сама определит тип ключа и откроет нужную форму
    ПоказатьЗначение(, КлючЗаписи);
    
КонецПроцедуры

Использование функций БСП (Библиотеки Стандартных Подсистем)

Если вы ведете разработку в типовой конфигурации 1С (например, «1С:Бухгалтерия», «1С:ERP» или «1С:Управление нашей фирмой»), то вам нет необходимости писать весь этот код вручную. В состав БСП уже входят готовые универсальные функции для безопасного открытия форм записей регистров.

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


// Пример использования БСП для открытия формы записи
Отбор = Новый Структура("Период, Пользователь", Данные.Период, Данные.Пользователь);
ЗначенияЗаполнения = Новый Структура("Период, Пользователь", Данные.Период, Данные.Пользователь);

ОбменДаннымиКлиент.ОткрытьФормуЗаписиРегистраСведенийПоОтбору(
    Отбор, 
    ЗначенияЗаполнения, 
    "ЗаметкиПользователя"
);

Использование механизмов БСП позволяет значительно сократить объем вашего кода, сделать его более читаемым и гарантирует совместимость с будущими обновлениями платформы «1С:Предприятие». Для быстрого поиска встроенных процедур и примеров их вызова можно использовать справочник по методам БСП.

← На главную