Как правильно получить остатки товаров на момент документа с помощью виртуальной таблицы "Остатки" в 1С?

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

Приветствуем вас, коллеги! Сегодня мы подробно разберем распространенную ситуацию при работе с отчетами по регистрам накопления в 1С:Предприятии, когда возникает необходимость получить остатки по товарам не на текущий момент, а строго на момент проведения конкретного документа. Мы выясним, почему стандартный подход с фильтрацией по номенклатуре из документа не дает ожидаемого результата, и как правильно использовать параметры виртуальной таблицы «Остатки» для решения этой задачи.

Анализируем проблему: почему "Остатки" показывают не то, что ожидается?

Представим ситуацию: вы создали отчет, который должен показать остатки товаров, фигурирующих в выбранном документе «Реализация товаров». Для этого вы используете виртуальную таблицу регистра накопления «ОстаткиТоваров.Остатки» и в подзапросе указываете условие, чтобы номенклатура выбиралась только из табличной части «Товары» интересующего документа. Например, запрос выглядит примерно так:


ВЫБРАТЬ
    ОстаткиТоваровОстатки.Номенклатура,
    ОстаткиТоваровОстатки.КоличествоОстаток
ИЗ
    РегистрНакопления.ОстаткиТоваров.Остатки КАК ОстаткиТоваровОстатки
ГДЕ
    ОстаткиТоваровОстатки.Номенклатура В
        (ВЫБРАТЬ
            РеализацияТоваровТовары.Номенклатура
        ИЗ
            Документ.РеализацияТоваров.Товары КАК РеализацияТоваровТовары
        ГДЕ
            РеализацияТоваровТовары.Ссылка = &ДокументРеализации)

Вы ожидаете, что при выборе документа «Реализация Товаров 0...1», в отчете будут показаны остатки, как будто они получены *сразу после проведения именно этого документа*, например, «Ручка -10» и «Карандаш -30». Однако на практике вы видите, что отчет показывает текущие суммарные остатки по этой номенклатуре, учитывая все движения (и поступления, и реализации) по всем документам, а не только по выбранному. Например, «Ручка 190» и «Карандаш -10». Почему так происходит?

Разбираемся в механизме работы виртуальной таблицы "Остатки"

Ключ к пониманию кроется в принципе работы виртуальной таблицы `Остатки`. Эта таблица предназначена для получения актуальных остатков ресурсов на определенный момент времени. Если вы не указываете конкретный момент времени, она рассчитывает итоги по самым последним записям, фактически предоставляя **текущие остатки**. Когда вы используете подзапрос, чтобы выбрать номенклатуру из определенного документа (например, `РеализацияТоваровТовары.Номенклатура`), вы всего лишь формируете **список номенклатуры**. Виртуальная таблица `Остатки` затем берет этот список и для каждой позиции из него рассчитывает *ее текущий остаток* по регистру в целом. Она не "знает" и не "заботится" о том, из какого документа вы взяли этот список номенклатуры. Для нее важна только сама номенклатура и момент времени, на который нужно получить остаток. Таким образом, ваше условие в подзапросе `РеализацияТоваровТовары.Ссылка = &ДокументРеализации` служит исключительно для того, чтобы передать в основной запрос **список номенклатуры**, которая есть в табличной части `Товары` конкретного документа `РеализацияТоваров`. Оно *не фильтрует движения регистра* по этому документу, а лишь задает фильтр по измерениям регистра (в данном случае, по `Номенклатуре`).

Корректное решение: использование параметра "Граница" с "МоментомВремени"

Для того чтобы получить остатки на определенный момент времени, включая движения конкретного документа, нам необходимо использовать **первый параметр** виртуальной таблицы `Остатки`, который называется `Период`. Этот параметр принимает значение типа `Граница`. Давайте рассмотрим подробнее, что это значит и как это работает.

МоментВремени: точное позиционирование во времени

В 1С просто даты недостаточно для точного позиционирования. Несколько документов могут быть проведены в одну и ту же секунду. Чтобы обеспечить уникальную последовательность и однозначность, используется объект `МоментВремени`. `МоментВремени` – это комбинация даты, времени и ссылки на сам документ. Это позволяет системе точно определить место документа в общей хронологии движений.

Граница: включение или исключение движений документа

Объект `Граница` позволяет нам указать, на какую точку во времени мы хотим получить остатки, и как при этом следует учитывать движения документа, чей `МоментВремени` мы используем. * Если мы просто передадим `МоментВремени` документа в параметр `Период` виртуальной таблицы `Остатки`, по умолчанию остатки будут рассчитаны **на начало секунды**, в которую был проведен документ. Это означает, что движения самого этого документа учтены не будут. * Чтобы включить движения выбранного документа в расчет остатков, нам нужно создать `Границу` с видом `Включая` от `МоментаВремени` этого документа. Рассмотрим пример того, как это будет выглядеть в запросе и в коде 1С.

Изменение запроса для получения остатков на момент документа

Мы должны добавить первый параметр в виртуальную таблицу `Остатки`, передав в него нашу `Границу`.


ВЫБРАТЬ
    ОстаткиТоваровОстатки.Номенклатура,
    ОстаткиТоваровОстатки.КоличествоОстаток
ИЗ
    РегистрНакопления.ОстаткиТоваров.Остатки(
            &МоментДокументаГраница, // <<-- Первый параметр: граница
            РегистрНакопления.ОстаткиТоваров.Номенклатура В
                (ВЫБРАТЬ
                    РеализацияТоваровТовары.Номенклатура
                ИЗ
                    Документ.РеализацияТоваров.Товары КАК РеализацияТоваровТовары
                ГДЕ
                    РеализацияТоваровТовары.Ссылка = &ДокументРеализации) // Второй параметр: отбор по номенклатуре
        ) КАК ОстаткиТоваровОстатки

Установка параметра "МоментДокументаГраница" в коде 1С

Теперь нам нужно создать соответствующую `Границу` в коде 1С и передать ее в качестве параметра запроса.


// Предположим, &ДокументРеализации - это ссылка на выбранный пользователем документ
// в ПараметрахДанныхРасшифровки или из элемента формы отчета.

// 1. Получаем МоментВремени выбранного документа.
// Для этого сначала нужно получить объект Ссылка на документ.
// Если &ДокументРеализации уже является ссылкой, то
// МоментДокумента = &ДокументРеализации.МоментВремени();
// Если &ДокументРеализации это значение типа Строка (UUID) или неопределено,
// то нужно сначала преобразовать ее в Ссылку.
// Например, если &ДокументРеализации - это просто ID документа или ссылка
// на документ целиком, используем следующий подход:
СсылкаНаДокумент = &ДокументРеализации; // Принимаем, что &ДокументРеализации - это уже Ссылка
Если ТипЗнч(СсылкаНаДокумент) = Тип("ДокументСсылка.РеализацияТоваров") Тогда
    МоментДокумента = СсылкаНаДокумент.МоментВремени();
Иначе
    // Обработка случаев, когда передан неверный тип или значение
    // Например, если передан UUID
    Попытка
        СсылкаНаДокумент = Документы.РеализацияТоваров.ПолучитьСсылку(Новый УникальныйИдентификатор(&ДокументРеализации));
        МоментДокумента = СсылкаНаДокумент.МоментВремени();
    Исключение
        Сообщить("Не удалось получить МоментВремени для документа: " + ОписаниеОшибки());
        Возврат;
    КонецПопытки;
КонецЕсли;


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

// 3. Устанавливаем параметр запроса.
Запрос.УстановитьПараметр("МоментДокументаГраница", ГраницаДляЗапроса);
// Устанавливаем параметр для подзапроса
Запрос.УстановитьПараметр("ДокументРеализации", СсылкаНаДокумент);

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

Альтернатива: виртуальная таблица "ОстаткиИОбороты"

В некоторых случаях, если вам нужны не только остатки на определенный момент, но и обороты (изменения) за определенный период, или если задача подразумевает анализ движений *в рамках* самого документа, возможно, более подходящей будет виртуальная таблица `ОстаткиИОбороты`. Она позволяет получить и начальные/конечные остатки, и обороты за заданный период. Однако, если задача строго поставлена через `Остатки`, то использование `Границы` является обязательным.

Дополнительное пояснение: поле "Ссылка" в табличных частях

В процессе обсуждения часто возникают вопросы о том, что означает поле `РеализацияТоваровТовары.Ссылка`. Давайте проясним этот момент. В 1С:Предприятии табличные части документов (например, `Товары` у документа `РеализацияТоваров`) хранятся в отдельных таблицах базы данных. Для связи записей табличной части с документом-владельцем используется поле, которое традиционно называется `Ссылка`. * `РеализацияТоваровТовары` — это объект, представляющий одну строку табличной части `Товары` документа `РеализацияТоваров`. * `РеализацияТоваровТовары.Ссылка` — это поле, которое содержит **ссылку на сам документ "РеализацияТоваров"**, которому принадлежит данная строка табличной части. То есть, это указатель на "родительский" документ для каждой строки табличной части. Использование термина `Ссылка` вместо, например, `Владелец`, является устоявшейся практикой в платформе 1С и отражает технический механизм связывания данных.

Краткие рекомендации по составлению отчетов с регистрами накопления

1. **Остатки на момент:** Всегда помните, что виртуальная таблица `Остатки` без параметра `Период` (или с `Периодом` типа `Дата`) дает остатки на **конец указанного периода** (или на текущий момент). Для точного "момента документа" используйте `Границу` с `МоментомВремени` и видом `Включая`. 2. **Цель фильтрации:** Четко понимайте, что фильтрация по измерениям (например, `Номенклатура В (...)`) лишь ограничивает *выводимые позиции*, но не *момент расчета остатков*. 3. **Выбор виртуальной таблицы:** Если вам нужны только остатки на текущий момент, `Остатки` подойдет. Если нужны остатки на начало/конец периода и обороты за период, рассмотрите `ОстаткиИОбороты`. 4. **Производительность:** Избегайте лишних группировок в запросах к виртуальным таблицам `Остатки`, так как они уже сгруппированы по измерениям и ресурсам. 5. **Тестирование:** Всегда тестируйте ваши отчеты с разными входными данными и сравнивайте результаты с ожидаемыми, чтобы убедиться в корректности запросов.

Надеемся, что это подробное объяснение поможет вам избежать распространенных ошибок и эффективно создавать отчеты по регистрам накопления в 1С!

← На главную