Как получить цену номенклатуры по договору контрагента и устранить ошибку метаданных?

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

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

Причина ошибки «Значение не является значением объектного типа (метаданные)»

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

Ошибка возникает в следующих ситуациях:

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

Типичные ошибки в коде и способы их устранения

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

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

Оптимальное решение: заполнение табличной части одним запросом

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

Разберем по шагам логику построения запроса:

  1. Выбираем контрагентов из заданной группы с помощью условия В ИЕРАРХИИ, исключая группы и помеченные на удаление элементы.
  2. Соединяем контрагентов с их договорами (левое соединение по владельцу или по реквизиту ОсновнойДоговорКонтрагента).
  3. Левым соединением подключаем виртуальную таблицу РегистрСведений.ЦеныНоменклатуры.СрезПоследних. Условия по дате и списку номенклатуры передаем в параметры виртуальной таблицы, а связь выполняем одновременно по типу цен из договора и по номенклатуре.
  4. Для предотвращения появления значений NULL при отсутствии установленной цены используем функцию ЕСТЬNULL(Цена, 0).

Посмотрим на пример серверной процедуры заполнения:


&НаСервере
Процедура ЗаполнитьНаСервере()
	
	Запрос = Новый Запрос;
	Запрос.Текст = 
		"ВЫБРАТЬ
		|	Контрагенты.Ссылка КАК Студент,
		|	Контрагенты.Родитель КАК Группа,
		|	ДоговорыКонтрагентов.Ссылка КАК ДоговорКонтрагента,
		|	ЕСТЬNULL(ЦеныОбучение.Цена, 0) КАК СуммаОбучение,
		|	ЕСТЬNULL(ЦеныОбщежитие.Цена, 0) КАК СуммаОбщежитие
		|ИЗ
		|	Справочник.Контрагенты КАК Контрагенты
		|	ЛЕВОЕ СОЕДИНЕНИЕ Справочник.ДоговорыКонтрагентов КАК ДоговорыКонтрагентов
		|		ПО Контрагенты.ОсновнойДоговорКонтрагента = ДоговорыКонтрагентов.Ссылка
		|	ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ЦеныНоменклатуры.СрезПоследних(
		|			&Период,
		|			Номенклатура = &НоменклатураОбучение) КАК ЦеныОбучение
		|		ПО ДоговорыКонтрагентов.ТипЦен = ЦеныОбучение.ТипЦен
		|	ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ЦеныНоменклатуры.СрезПоследних(
		|			&Период,
		|			Номенклатура = &НоменклатураОбщежитие) КАК ЦеныОбщежитие
		|		ПО ДоговорыКонтрагентов.ТипЦен = ЦеныОбщежитие.ТипЦен
		|ГДЕ
		|	Контрагенты.Ссылка В ИЕРАРХИИ(&Группа)
		|	И НЕ Контрагенты.ЭтоГруппа
		|	И НЕ Контрагенты.ПометкаУдаления";
	
	Запрос.УстановитьПараметр("Группа", Объект.Группа);
	Запрос.УстановитьПараметр("Период", Объект.Дата);
	Запрос.УстановитьПараметр("НоменклатураОбучение", Объект.НачислениеЗаОбучение);
	Запрос.УстановитьПараметр("НоменклатураОбщежитие", Объект.НачислениеЗаОбщежитие);
	
	// Выполняем запрос и загружаем данные напрямую в табличную часть
	ТаблицаРезультата = Запрос.Выполнить().Выгрузить();
	Объект.НачисленияЗаОбучениеИОбщежитие.Загрузить(ТаблицаРезультата);
	
КонецПроцедуры

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

← На главную