Как правильно вывести остатки номенклатуры в 1С с учетом списаний?

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

При разработке механизмов учета товаров или материалов в системе 1С:Предприятие 8.3 программисты часто сталкиваются с ситуацией, когда запрос к виртуальной таблице остатков возвращает некорректные данные. Чаще всего проблема проявляется в том, что текущий документ не «видит» предыдущих списаний или, наоборот, не учитывает собственный приход. Разберем подробно, как правильно составить запрос, настроить параметры и оптимизировать код для получения актуальных остатков.

Анализируем причину ошибки: почему не учитываются списания?

В исходном коде автора основная проблема заключалась в неправильном определении момента времени, на который запрашиваются данные. Если мы передаем в параметр &Дата просто дату документа, система может повести себя неоднозначно, особенно если в одну и ту же секунду в базе зарегистрировано несколько документов.

Рассмотрим основные факторы, влияющие на результат:

  1. Объект Граница: Для получения остатков «ДО» совершения движений текущим документом необходимо использовать объект Граница с видом ВидГраницы.Исключая.
  2. Поиск по наименованию: Использование НайтиПоНаименованию внутри программного кода — это плохая практика. Она замедляет работу и может привести к ошибкам, если в справочнике окажутся элементы с одинаковыми именами. Необходимо передавать готовую ссылку Объект.МестаХранения.
  3. Виртуальные таблицы: Параметры (условия) нужно накладывать именно в скобках виртуальной таблицы, а не в секции ГДЕ.

Шаг 1: Настройка правильной границы времени

Для того чтобы документ «увидел» все списания, которые произошли до него, мы должны четко указать системе, в какой точке временной шкалы мы находимся. Проанализируем, как это реализовать программно:


Если Объект.Ссылка.Пустая() Тогда
    // Если документ еще не записан, берем остатки на конец дня текущей даты
    Д = КонецДня(Объект.Дата);
Иначе
    // Если документ существует, берем остатки на его МоментВремени, исключая его собственные движения
    Д = Новый Граница(Объект.Ссылка.МоментВремени(), ВидГраницы.Исключая);
КонецЕсли;

Использование ВидГраницы.Исключая гарантирует, что если вы перепроводите старый документ, запрос покажет состояние склада именно на тот момент, когда этот документ только собирался списать товар.

Шаг 2: Оптимизация запроса и расчет себестоимости

Часто новички вычисляют себестоимость уже после выполнения запроса, перебирая выборку. Однако гораздо эффективнее перенести все расчеты на сторону СУБД. Это уменьшит объем данных, передаваемых между сервером базы данных и сервером приложений 1С.

Разберем пример запроса, где расчет себестоимости выполняется сразу с проверкой на деление на ноль с помощью оператора ВЫБОР:


Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |	ОстаткиМатериалов.КоличествоОстаток КАК Остаток,
    |	ВЫБОР
    |		КОГДА ОстаткиМатериалов.КоличествоОстаток > 0
    |			ТОГДА ОстаткиМатериалов.СуммаОстаток / ОстаткиМатериалов.КоличествоОстаток
    |		ИНАЧЕ 0
    |	КОНЕЦ КАК Себестоимость
    |ИЗ
    |	РегистрНакопления.ОстаткиМатериалов.Остатки(&Дата, Материал = &Материал И МестаХранения = &МестаХранения) КАК ОстаткиМатериалов";

Запрос.УстановитьПараметр("Дата", Д);
Запрос.УстановитьПараметр("Материал", МатериалСсылка);
Запрос.УстановитьПараметр("МестаХранения", Объект.МестаХранения);

Обратите внимание на использование параметров виртуальной таблицы. Мы передаем Материал и МестаХранения прямо в скобки Остатки(). Это позволяет платформе 1С оптимизировать SQL-запрос так, чтобы он выбирал из итогов только нужные записи, а не всю таблицу целиком.

Шаг 3: Обработка пустых значений (NULL)

Если по конкретному материалу на складе вообще нет остатков, виртуальная таблица может не вернуть ни одной строки. Если же мы соединяем таблицу документа с таблицей остатков, в полях остатка появится значение NULL. Проанализируем ситуацию: попытка сложения NULL с числом в коде 1С приведет к ошибке, а в запросе результат будет также равен NULL.

Чтобы избежать этого, всегда используйте функцию ЕСТЬNULL в тексте запроса:


"ВЫБРАТЬ
|	ЕСТЬNULL(ОстаткиМатериалов.КоличествоОстаток, 0) КАК Остаток,
|	ЕСТЬNULL(ОстаткиМатериалов.СуммаОстаток, 0) КАК Сумма
|ИЗ ..."

Проблема "Запроса в цикле"

В примере из обсуждения на форуме функция ВсеОстатки принимает один материал. Это наводит на мысль, что функция вызывается в цикле при обходе табличной части. Это критическая ошибка производительности. Рассмотрим, как сделать правильно.

Вместо того чтобы 100 раз обращаться к базе данных за остатком каждого товара, выполним один запрос:

  1. Выгрузим все материалы из табличной части в массив.
  2. Передадим этот массив в параметр запроса (используя оператор В вместо =).
  3. Получим таблицу остатков и загрузим ее в Соответствие или ВременнуюТаблицу.

Посмотрим на пример кода для массового получения остатков:


// 1. Собираем материалы
МатериалыМассив = Объект.Материалы.ВыгрузитьКолонку("Материал");

// 2. Запрос
Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |	Остатки.Материал КАК Материал,
    |	Остатки.КоличествоОстаток КАК Остаток
    |ИЗ
    |	РегистрНакопления.ОстаткиМатериалов.Остатки(&Дата, Материал В (&МатериалыМассив) И МестаХранения = &МестаХранения) КАК Остатки";

Запрос.УстановитьПараметр("Дата", Д);
Запрос.УстановитьПараметр("МатериалыМассив", МатериалыМассив);
Запрос.УстановитьПараметр("МестаХранения", Объект.МестаХранения);

ТаблицаОстатков = Запрос.Выполнить().Выгрузить();

// 3. Быстрый поиск через соответствие
ОстаткиСклад = Новый Соответствие;
Для Каждого Стр Из ТаблицаОстатков Цикл
    ОстаткиСклад.Вставить(Стр.Материал, Стр.Остаток);
КонецЦикла;

// Теперь в цикле по строкам документа просто берем данные из соответствия
Для Каждого СтрокаТЧ Из Объект.Материалы Цикл
    ТекущийОстаток = ОстаткиСклад[СтрокаТЧ.Материал];
    Если ТекущийОстаток = Неопределено Тогда
        ТекущийОстаток = 0;
    КонецЕсли;
    // ... дальнейшая логика
КонецЦикла;

Итоговые рекомендации

Подведем итоги нашего разбора. Чтобы вывод остатков всегда был корректным и учитывал все предыдущие списания:

Применяя эти правила, вы создадите надежную систему складского учета, которая будет работать быстро и без ошибок даже при большом объеме данных и высокой интенсивности работы пользователей.

← На главную