При изучении программирования в системе «1С:Предприятие» по знаменитой книге М. Радченко «Практическое пособие разработчика», многие начинающие специалисты сталкиваются с трудностями в упражнении №14. Одной из самых частых ошибок является исключение «Поле объекта не обнаружено (Стоимость)» в модуле объекта документа ОказаниеУслуги. Давайте вместе разберем причины этой ошибки, проанализируем код и найдем правильное решение, а также рассмотрим важные нюансы работы с запросами в 1С.
Ошибка возникает в процедуре ОбработкаПроведения. Если мы внимательно посмотрим на листинг кода, предоставленный автором вопроса, мы увидим, что в модуле используется два последовательных запроса к базе данных. Основная проблема кроется в невнимательности при обращении к результатам этих запросов.
Рассмотрим фрагмент кода, где происходит ошибка:
Результат = Запрос2.Выполнить();
ВыборкаДетальныеЗаписи = РезультатЗапроса.Выбрать();
Проанализируем ситуацию: переменная РезультатЗапроса (использованная во второй строке) была инициализирована гораздо выше по коду — она хранит результат выполнения первого запроса. Первый запрос в данном упражнении предназначен лишь для помещения данных во временную таблицу НоменклатураДокумента и он не содержит поля Стоимость.
Второй запрос (Запрос2) как раз таки соединяет временную таблицу с регистрами остатков и выбирает поле Стоимость. Но, вызывая метод Выбрать() у первого результата (РезультатЗапроса), мы получаем выборку, в которой нужной колонки просто не существует. Отсюда и закономерная реакция платформы: Поле объекта не обнаружено (Стоимость).
Для исправления ситуации нам необходимо убедиться, что выборка создается из результата именно того запроса, который содержит необходимые данные. Пройдем по шагам процесс исправления:
Результат = Запрос2.Выполнить();.Выбрать(). Вместо РезультатЗапроса (старый результат) напишем Результат (актуальный результат).Правильный фрагмент кода должен выглядеть так:
// Выполняем второй запрос, который реально возвращает данные с остатками и стоимостью
Результат = Запрос2.Выполнить();
// Создаем выборку именно из ЭТОГО результата
ВыборкаДетальныеЗаписи = Результат.Выбрать();
Пока ВыборкаДетальныеЗаписи.Следующий() Цикл
// Теперь поле Стоимость будет доступно в выборке
СтоимостьМатериала = ВыборкаДетальныеЗаписи.Стоимость / ВыборкаДетальныеЗаписи.Количество;
// ... дальнейшая логика
КонецЦикла;
Исправление имени переменной устранит критическую ошибку, при которой конфигурация «вылетает». Однако, если мы проанализируем текст Запрос2 в исходном сообщении, мы обнаружим еще две серьезные логические ошибки в условиях соединения таблиц (блоки ПО), которые могут привести к неверным расчетам:
Ошибка в соединении по стоимости:
У автора: ПО НоменклатураДокумента.Номенклатура = СтоимостьМатериаловОстатки.СтоимостьОстаток.
Это в корне неверно. Мы должны сопоставлять ссылку на номенклатуру с измерением регистра, а не со значением остатка суммы.
Как правильно: ПО НоменклатураДокумента.Номенклатура = СтоимостьМатериаловОстатки.Материал.
Ошибка в соединении по количеству:
У автора: ПО НоменклатураДокумента.Номенклатура = ОстаткиМатериаловОстатки.КоличествоОстаток.
Ситуация аналогичная — нельзя приравнивать объект (Номенклатуру) к числу (Количеству).
Как правильно: ПО НоменклатураДокумента.Номенклатура = ОстаткиМатериаловОстатки.Материал.
Хотя в учебном пособии для наглядности используется МенеджерВременныхТаблиц и два разных объекта Запрос, в реальной практике разработки на платформе 1С 8.3 рекомендуется использовать пакетные запросы. Это позволяет объединить несколько запросов в один текст, разделяя их символом точки с запятой ;.
Преимущества пакетного запроса:
Рассмотрим, как мог бы выглядеть упрощенный пакетный запрос для данной задачи:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ТаблицаТовары.Номенклатура КАК Номенклатура,
| СУММА(ТаблицаТовары.Количество) КАК КоличествоВДокументе
|ПОМЕСТИТЬ ВТ_Товары
|ИЗ
| Документ.ОказаниеУслуги.ПереченьНоменклатуры КАК ТаблицаТовары
|ГДЕ
| ТаблицаТовары.Ссылка = &Ссылка
|СГРУППИРОВАТЬ ПО
| ТаблицаТовары.Номенклатура
|;
|
|ВЫБРАТЬ
| ВТ_Товары.Номенклатура,
| ВТ_Товары.КоличествоВДокументе,
| ЕСТЬNULL(Остатки.СтоимостьОстаток, 0) КАК Стоимость,
| ЕСТЬNULL(Остатки.КоличествоОстаток, 0) КАК Количество
|ИЗ
| ВТ_Товары КАК ВТ_Товары
| ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.СтоимостьМатериалов.Остатки(&МоментВремени, ) КАК Остатки
| ПО ВТ_Товары.Номенклатура = Остатки.Материал";
Запрос.УстановитьПараметр("Ссылка", Ссылка);
Запрос.УстановитьПараметр("МоментВремени", МоментВремени());
РезультатПакета = Запрос.ВыполнитьПакет();
// Результат последнего запроса в пакете будет в последнем элементе массива
Выборка = РезультатПакета[РезультатПакета.Количество() - 1].Выбрать();
В коде из упражнения используется так называемая «старая методика проведения». Мы сначала принудительно записываем пустые наборы записей (Движения.СтоимостьМатериалов.Записать();), чтобы текущие движения документа не влияли на остатки при запросе.
Выясним несколько важных моментов, которые помогут в будущем:
МоментВремени(). Если этого не сделать, запрос может вернуть остатки на начало секунды или на текущее время, что приведет к ошибкам при перепроведении документов «задним числом».Стоимость / Количество) всегда делайте проверку делителя на ноль. В коде автора это реализовано через Если ВыборкаДетальныеЗаписи.Количество = 0, что является абсолютно правильной практикой.NULL, если товара нет на складе. Обязательно используйте функцию ЕСТЬNULL(..., 0) в тексте запроса, чтобы избежать ошибок при арифметических операциях в коде 1С.Проанализировав ситуацию, мы видим, что обучение по книгам требует предельной концентрации внимания. Ошибка «Поле объекта не обнаружено» почти всегда указывает на то, что вы обращаетесь не к тому объекту или забыли добавить поле в список выбора запроса. В данном случае исправление имени переменной РезультатЗапроса на Результат полностью решает проблему и позволяет продолжить обучение по практическому пособию.