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