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

Программист 1С v7.7 Управленческий учет IT и автоматизация бизнеса
← На главную

При переходе к разработке на платформе 1С:Предприятие 7.7 разработчики часто сталкиваются с особенностями встроенного языка запросов и работы с регистрами. Типичная задача — при заполнении табличной части документа определить наличие свободного остатка товара на складах (подразделениях) и подставить подразделение с положительным остатком. Разберем по шагам характерные ошибки, возникающие при составлении запросов в 1С 7.7, а также рассмотрим оптимальные варианты решения этой задачи.

Анализ типичных ошибок при работе с запросами в 1С 7.7

Рассмотрим исходную ситуацию: при попытке выполнить запрос к регистру остатков результирующая таблица значений оказывается пустой либо содержит некорректные пустые строки. Выделим основные причины такого поведения:

  1. Отсутствие секции Группировка: в языке запросов 1С 7.7 итоговые функции (например, КонОст()) не рассчитываются без явного указания группировок измерений. Без группировки запрос не может сформировать строки выборки.
  2. Синтаксические ошибки в условиях и строках: каждая строка описания запроса в 1С 7.7 должна завершаться точкой с запятой ;. Конструкция условия пишется строго в виде |Условие(Выражение);, а не через вынос оператора сравнения за скобки.
  3. Выгрузка иерархических итогов справочников: по умолчанию группировка по справочнику в запросе строит иерархию (папки и вышестоящие уровни). При выгрузке в плоскую таблицу значений это приводит к появлению строк с пустыми значениями и промежуточными суммами. Чтобы получать только конечные элементы, необходимо использовать модификатор Без Групп.
  4. Пересечение имен переменных: если переменная запроса названа так же, как реквизит формы документа или локальная переменная модуля (например, Номенклатура), это может приводить к неоднозначности при вычислении условия. Рекомендуется добавлять префиксы к переменным запроса (например, зНоменклатура, зПодразделение).
  5. Вызов запроса внутри цикла: выполнение запроса на каждой итерации обхода строк или подчиненного справочника является критической ошибкой производительности. Запрос должен выполняться один раз для всего набора данных.
  6. Особенности позиционирования в таблице значений: перед обходом выгруженной таблицы значений через ПолучитьСтроку() необходимо обязательно вызвать метод ВыбратьСтроки().

Способ 1. Получение остатков через один пакетный запрос

Разберем правильную архитектуру выборки данных с помощью объекта Запрос. Вместо выполнения запроса в цикле мы сначала соберем список нужных позиций в СписокЗначений, выполним один запрос с отбором по подразделениям и номенклатуре, после чего выгрузим результат в таблицу значений для быстрого сопоставления.

Посмотрим на пример реализации:


// 1. Собираем список номенклатуры из подчиненного справочника
СписНоменклатуры = СоздатьОбъект("СписокЗначений");
Товары = СоздатьОбъект("Справочник.СпецпредложенияТовары");
Товары.ИспользоватьВладельца(Пакет);
Товары.ВыбратьЭлементы();
Пока Товары.ПолучитьЭлемент() = 1 Цикл
    Если Товары.ПометкаУдаления() = 0 Тогда
        СписНоменклатуры.ДобавитьЗначение(Товары.Номенклатура);
    КонецЕсли;
КонецЦикла;

// 2. Формируем и выполняем единый запрос к регистру остатков
Запрос = СоздатьОбъект("Запрос");
ТекстЗапроса = 
"//{{ЗАПРОС(ОстаткиПоСкладам)
|зНоменклатура = Регистр.Склад.Номенклатура;
|зПодразделение = Регистр.Склад.Подразделение;
|Количество = Регистр.Склад.Количество;
|Функция КоличествоКонОст = КонОст(Количество);
|Группировка зНоменклатура без групп;
|Группировка зПодразделение без групп;
|Условие(зНоменклатура в СписНоменклатуры);
|"//}}ЗАПРОС
;

ТЗОстатков = СоздатьОбъект("ТаблицаЗначений");
Если Запрос.Выполнить(ТекстЗапроса) = 1 Тогда
    // Выгружаем только детальные записи без итоговых строк группировок
    Запрос.Выгрузить(ТЗОстатков, 1, 0);
КонецЕсли;

// 3. Заполняем строки документа, находя остатки в полученной ТЗ
Товары.ВыбратьЭлементы();
Пока Товары.ПолучитьЭлемент() = 1 Цикл
    Если Товары.ПометкаУдаления() = 0 Тогда
        НоваяСтрока();
        Номенклатура = Товары.Номенклатура;
        Количество = 1;
        
        // Ищем первое подразделение с остатком >= 1
        ТЗОстатков.ВыбратьСтроки();
        Пока ТЗОстатков.ПолучитьСтроку() = 1 Цикл
            Если (ТЗОстатков.зНоменклатура = Товары.Номенклатура) И (ТЗОстатков.КоличествоКонОст >= 1) Тогда
                Подразделение = ТЗОстатков.зПодразделение;
                Прервать;
            КонецЕсли;
        КонецЦикла;
    КонецЕсли;
КонецЦикла;

Обратим внимание на параметры метода Выгрузить(ТЗОстатков, 1, 0): передача (1, 0) формирует плоскую структуру колонок и исключает промежуточные строки итогов.

Способ 2. Получение остатков напрямую через объект «Регистр»

Если требуется получить остатки без использования сложного механизма запросов, в платформе 1С 7.7 предусмотрены прямые методы работы с итогами регистров остатков.

Вариант А: Использование метода ВыгрузитьИтоги()

Этот способ позволяет получить снимок текущих остатков регистра по всем измерениям в одну таблицу значений за одно обращение к базе данных:


РегСклад = СоздатьОбъект("Регистр.Склад");
ТЗОстатков = СоздатьОбъект("ТаблицаЗначений");

// Выгружаем измерения (1) и ресурсы (1) регистра
РегСклад.ВыгрузитьИтоги(ТЗОстатков, 1, 1);

// После этого обходим товары и ищем строки в ТЗОстатков
Товары = СоздатьОбъект("Справочник.СпецпредложенияТовары");
Товары.ИспользоватьВладельца(Пакет);
Товары.ВыбратьЭлементы();
Пока Товары.ПолучитьЭлемент() = 1 Цикл
    Если Товары.ПометкаУдаления() = 0 Тогда
        НоваяСтрока();
        Номенклатура = Товары.Номенклатура;
        Количество = 1;
        
        ТЗОстатков.ВыбратьСтроки();
        Пока ТЗОстатков.ПолучитьСтроку() = 1 Цикл
            Если (ТЗОстатков.Номенклатура = Товары.Номенклатура) И (ТЗОстатков.Количество >= 1) Тогда
                Подразделение = ТЗОстатков.Подразделение;
                Прервать;
            КонецЕсли;
        КонецЦикла;
    КонецЕсли;
КонецЦикла;

Вариант Б: Использование методов УстановитьЗначениеФильтра() и ВыбратьИтоги()

Если для измерения Номенклатура в конфигураторе включен признак «Отбор» (быстрая фильтрация движений), можно перебирать остатки конкретной позиции напрямую через регистр:


РегСклад = СоздатьОбъект("Регистр.Склад");

Товары = СоздатьОбъект("Справочник.СпецпредложенияТовары");
Товары.ИспользоватьВладельца(Пакет);
Товары.ВыбратьЭлементы();
Пока Товары.ПолучитьЭлемент() = 1 Цикл
    Если Товары.ПометкаУдаления() = 0 Тогда
        НоваяСтрока();
        Номенклатура = Товары.Номенклатура;
        Количество = 1;
        
        // Устанавливаем фильтр по текущей номенклатуре
        РегСклад.УстановитьЗначениеФильтра("Номенклатура", Товары.Номенклатура, 1);
        РегСклад.ВыбратьИтоги();
        Пока РегСклад.ПолучитьИтог() = 1 Цикл
            Если РегСклад.Количество >= 1 Тогда
                Подразделение = РегСклад.Подразделение;
                Прервать;
            КонецЕсли;
        КонецЦикла;
    КонецЕсли;
КонецЦикла;

Важные рекомендации по работе с регистрами в 1С 7.7

  1. Учет Точки Актуальности (ТА): если в тексте запроса или при обращении к объекту Регистр период не указан явно, данные берутся на текущую ТА. При заполнении исторических документов (проведенных ранее ТА) необходимо задавать период расчета остатков: |Период по ТекущийДокумент; или вызывать РегСклад.ВременныйРасчет(1); РегСклад.РассчитатьРегистрыНа(ТекущийДокумент);.
  2. Программное заполнение реквизитов формы: избегайте вызова процедуры ПриВыбореЗначения() внутри алгоритмов массового заполнения строк, так как эта процедура предназначена исключительно для реакции на интерактивные действия пользователя. Заполняйте реквизиты строк напрямую в коде.
← На главную