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