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