Как исправить ошибку «Поле объекта не обнаружено» при установке отбора в наборе записей регистра сведений?

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

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

Почему возникает ошибка «Поле объекта не обнаружено»?

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

Сценарий 1. Попытка установить отбор по измерению в подчиненном регистре.

В коде автора присутствует следующая строка:


НаборЗаписей = РегистрыСведений.Бригады.СоздатьНаборЗаписей();
НаборЗаписей.Отбор.ТранспортноеСредство.Установить(ТС);

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

Сценарий 2. Ошибка при указании имени документа вместо предопределенного поля.

Пытаясь обойти первую ошибку, разработчики часто совершают вторую ошибку, указывая имя конкретного документа-регистратора:

НаборЗаписей.Отбор.ОформлениеБригады.Установить(СсылкаНаДокумент);

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

НаборЗаписей.Отбор.Регистратор.Установить(СсылкаНаДокумент); ---

Глубокий анализ архитектурной проблемы (Антипаттерн)

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

Такой подход является грубым нарушением стандартов разработки «1С» (антипаттерном) по следующим причинам:

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

---

Правильные варианты реализации задачи

Рассмотрим подробнее четыре архитектурно верных способа решения этой задачи в зависимости от бизнес-требований вашей системы.

Вариант А. Перевод регистра сведений в статус «Периодический» (Рекомендуемый путь)

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

Как это настроить и использовать:

Пример запроса для получения актуальной бригады:


Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |	БригадыСрезПоследних.Грузчик1,
    |	БригадыСрезПоследних.Грузчик2,
    |	БригадыСрезПоследних.Водитель
    |ИЗ
    |	РегистрСведений.Бригады.СрезПоследних(&Период, ТранспортноеСредство = &ТранспортноеСредство) КАК БригадыСрезПоследних";

Запрос.УстановитьПараметр("Период", ТекущаяДата());
Запрос.УстановитьПараметр("ТранспортноеСредство", ВыбранноеТС);
РезультатЗапроса = Запрос.Выполнить();

Вариант Б. Перевод регистра сведений в статус «Независимый»

Если история изменений вам абсолютно не важна и на одну машину всегда может быть назначена строго одна бригада (актуальное состояние), то жесткая привязка регистра к документам-регистраторам избыточна. Сделайте регистр независимым.

Как это настроить и использовать:


НаборЗаписей = РегистрыСведений.Бригады.СоздатьНаборЗаписей();
// Здесь отбор по измерению работает без ошибок, так как регистр независимый!
НаборЗаписей.Отбор.ТранспортноеСредство.Установить(ТранспортноеСредство);

// Записываем пустой набор записей — это действие очистит в базе данных все строки по данному автомобилю
НаборЗаписей.Записать(); 

// Теперь можно создать и записать новую актуальную запись
НоваяЗапись = НаборЗаписей.Добавить();
НоваяЗапись.ТранспортноеСредство = ТранспортноеСредство;
НоваяЗапись.Водитель = Водитель;
НоваяЗапись.Грузчик1 = Грузчик1;
НоваяЗапись.Грузчик2 = Грузчик2;
НаборЗаписей.Записать();

Вариант В. Очистка движений через поиск регистратора (Если архитектуру менять нельзя)

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

Алгоритм действий:

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

Посмотрим на пример кода:


// Шаг 1. Ищем старый регистратор запросом
Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |	Бригады.Регистратор КАК СтарыйРегистратор
    |ИЗ
    |	РегистрСведений.Бригады КАК Бригады
    |ГДЕ
    |	Бригады.ТранспортноеСредство = &ТранспортноеСредство";

Запрос.УстановитьПараметр("ТранспортноеСредство", ТранспортноеСредство);
Выборка = Запрос.Выполнить().Выбрать();

Если Выборка.Следующий() Тогда
    СтарыйРегистратор = Выборка.СтарыйРегистратор;
    
    // Шаг 2. Читаем движения старого документа
    НаборСтарый = РегистрыСведений.Бригады.СоздатьНаборЗаписей();
    НаборСтарый.Отбор.Регистратор.Установить(СтарыйРегистратор);
    НаборСтарый.Прочитать();
    
    // Шаг 3. Удаляем нужную строку из набора
    ИндексСтроки = НаборСтарый.Количество() - 1;
    Пока ИндексСтроки >= 0 Цикл
        СтрокаЗаписи = НаборСтарый[ИндексСтроки];
        Если СтрокаЗаписи.ТранспортноеСредство = ТранспортноеСредство Тогда
            НаборСтарый.Удалить(СтрокаЗаписи);
        КонецЕсли;
        ИндексСтроки = ИндексСтроки - 1;
    КонецЦикла;
    
    // Шаг 4. Перезаписываем движения старого документа (уже без нашего авто)
    НаборСтарый.Записать();
КонецЕсли;

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

---

Резюме по работе с набором записей

Давайте зафиксируем ключевые правила работы с объектом НаборЗаписей:

← На главную