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