Почему возникает ошибка «Поле объекта не обнаружено (Количество)» при проведении документа?

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

При изучении механизмов проведения документов и списания себестоимости разработчики часто сталкиваются с исключением во время выполнения процедуры ОбработкаПроведения. Ошибка указывает на отсутствие поля в выборке результата запроса при расчете стоимости списания:


{Документ.ОказаниеУслуги.МодульОбъекта(84)}: Поле объекта не обнаружено (Количество)
Если ВыборкаДетальныеЗаписи.Количество = 0 Тогда

Разберем по шагам, почему возникает эта ошибка, как Конструктор запросов назначает имена колонкам и как правильно организовать алгоритм списания себестоимости и контроля остатков.

Анализ причины ошибки

Выясним причину некорректного поведения. В цикле обхода выборки разработчик обращается к полям ВыборкаДетальныеЗаписи.Количество и ВыборкаДетальныеЗаписи.Стоимость:


Пока ВыборкаДетальныеЗаписи.Следующий() Цикл
    Если ВыборкаДетальныеЗаписи.Количество = 0 Тогда
        СтоимостьМатериала = 0;
    Иначе
        СтоимостьМатериала = ВыборкаДетальныеЗаписи.Стоимость / ВыборкаДетальныеЗаписи.Количество;
    КонецЕсли;
    // Формирование движений по регистрам...
КонецЦикла;

Посмотрим на текст запроса, сформированный разработчиком:


Запрос2 = Новый Запрос;
Запрос2.МенеджерВременныхТаблиц = МенеджерВТ;
Запрос2.Текст = "ВЫБРАТЬ
                |    НоменклатураДокумента.Номенклатура,
                |    НоменклатураДокумента.ВидНоменклатуры,
                |    НоменклатураДокумента.КоличествоВДокументе,
                |    НоменклатураДокумента.СуммаВДокументе,
                |    ЕСТЬNULL(СтоимостьМатериаловОстатки.СтоимостьОстаток, 0) КАК Поле1,
                |    ЕСТЬNULL(ОстаткиМатериаловОстатки.КоличествоОстаток, 0) КАК Поле2
                |ИЗ
                |    НоменклатураДокумента КАК НоменклатураДокумента
                |    ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.СтоимостьМатериалов.Остатки(
                |            ,
                |            Материал В (ВЫБРАТЬ НоменклатураДокумента.Номенклатура ИЗ НоменклатураДокумента)) КАК СтоимостьМатериаловОстатки
                |    ПО НоменклатураДокумента.Номенклатура = СтоимостьМатериаловОстатки.Материал
                |    ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ОстаткиМатериалов.Остатки(
                |            ,
                |            Материал В (ВЫБРАТЬ НоменклатураДокумента.Номенклатура ИЗ НоменклатураДокумента)) КАК ОстаткиМатериаловОстатки
                |    ПО НоменклатураДокумента.Номенклатура = ОстаткиМатериаловОстатки.Материал";

Проанализируем ситуацию: в тексте запроса для функций ЕСТЬNULL были назначены автоматические псевдонимы Поле1 и Поле2. Когда мы используем Конструктор запросов и добавляем вычисляемые выражения (функции, условия или арифметические операции), платформа 1С автоматически подставляет стандартные имена колонок вида ПолеN, если пользователь не указал псевдоним вручную.

В результате объект ВыборкаИзРезультатаЗапроса не содержит свойств с именами Стоимость и Количество. При попытке обращения к ним возникает исключение «Поле объекта не обнаружено».

Пошаговое исправление ошибки

Для исправления проблемы выполним следующие действия:

  1. Откроем текст запроса вручную или запустим Конструктор запросов.
  2. На вкладке «Объединения/Псевдонимы» (или непосредственно в тексте запроса с помощью ключевого слова КАК) переименуем Поле1 в Стоимость, а Поле2 — в Количество.

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


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

// Установим необходимость блокировки данных в регистрах
Движения.СтоимостьМатериалов.БлокироватьДляИзменения = Истина;
Движения.ОстаткиМатериалов.БлокироватьДляИзменения = Истина;

// Запишем пустые наборы записей, чтобы читать остатки без учета текущего документа
Движения.СтоимостьМатериалов.Записать();
Движения.ОстаткиМатериалов.Записать();

РезультатЗапроса = Запрос2.Выполнить();
ВыборкаДетальныеЗаписи = РезультатЗапроса.Выбрать();

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

Движения.Записать();

Разница между методом и полем выборки

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

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

Рассмотрим подробнее архитектурные нюансы, которые следует учитывать при реализации подобных механизмов:

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