Почему запрос контроля остатков при проведении документа возвращает пустой результат?

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

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

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


ВЫБРАТЬ
    ТоварыВДокументе.Товар КАК Товар,
    СУММА(ТоварыВДокументе.Количество) КАК Количество,
    МАКСИМУМ(ТоварыВДокументе.НомерСтроки) КАК НомерСтроки
ПОМЕСТИТЬ ТребуетсяТовара
ИЗ
    Документ.РасходТовара.Товары КАК ТоварыВДокументе
ГДЕ
    ТоварыВДокументе.Ссылка = &Ссылка
    И ТоварыВДокументе.Товар.Вид = ЗНАЧЕНИЕ(Перечисление.ВидыТоваров.Товар)
СГРУППИРОВАТЬ ПО
    ТоварыВДокументе.Товар
ИНДЕКСИРОВАТЬ ПО
    Товар
;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    ПРЕДСТАВЛЕНИЕ(ТребуетсяТовара.Товар) КАК ТоварПредставление,
    ВЫБОР
        КОГДА - ЕСТЬNULL(ТоварныеЗапасыОстатки.КоличествоОстаток, 0) > ТоварыВДокументе.Количество
            ТОГДА ТоварыВДокументе.Количество
        ИНАЧЕ - ЕСТЬNULL(ТоварныеЗапасыОстатки.КоличествоОстаток, 0)
    КОНЕЦ КАК Нехватка,
    ТребуетсяТовара.НомерСтроки КАК НомерСтроки
ИЗ
    ТребуетсяТовара КАК ТребуетсяТовара
        ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ТоварныеЗапасы.Остатки(
                ,
                Товар В
                    (ВЫБРАТЬ
                        ТребуетсяТовара.Товар
                    ИЗ
                        ТребуетсяТовара)
                    И Склад = &Склад) КАК ТоварныеЗапасыОстатки
        ПО ТребуетсяТовара.Товар = ТоварныеЗапасыОстатки.Товар
        ЛЕВОЕ СОЕДИНЕНИЕ Документ.РасходТовара.Товары КАК ТоварыВДокументе
        ПО ТребуетсяТовара.Товар = ТоварыВДокументе.Товар
            И ТребуетсяТовара.НомерСтроки = ТоварыВДокументе.НомерСтроки
ГДЕ
    ТоварыВДокументе.Ссылка = &Ссылка И
    0 > ЕСТЬNULL(ТоварныеЗапасыОстатки.КоличествоОстаток, 0)

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

Причина 1. Несоответствие методики проведения и условия на отрицательный остаток

Обратим внимание на условие в секции ГДЕ:


ГДЕ
    0 > ЕСТЬNULL(ТоварныеЗапасыОстатки.КоличествоОстаток, 0)

Данное условие эквивалентно проверке КоличествоОстаток < 0. Это классический признак новой методики проведения. Выясним, как она устроена:

  1. Сначала движения документа формируются и сразу записываются в базу данных с помощью конструкции Движения.ТоварныеЗапасы.Записывать = Истина; Движения.ТоварныеЗапасы.Записать();.
  2. При записи движений остаток на складе уменьшается. Если товара было недостаточно, остаток уходит в минус.
  3. Только после фактической записи движений выполняется запрос, который ищет позиции с отрицательными остатками (КоличествоОстаток < 0). Если такие строки найдены — проведение отменяется, а пользователю выводится сообщение об ошибке.

Если же движения документа еще не были записаны перед выполнением запроса (применяется старая методика проведения), то отрицательный остаток в регистре просто не образуется. Соответственно, условие 0 > ... отсекает абсолютно все строки, и выборка оказывается пустой.

Причина 2. Избыточное левое соединение с табличной частью документа

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

Посмотрим на ошибки, возникающие из-за повторного соединения:

  1. Все необходимые данные по товарам, их сгруппированному количеству и номерам строк уже были получены и помещены во временную таблицу ТребуетсяТовара. Повторное соединение с табличной частью Документ.РасходТовара.Товары является лишним и снижает производительность.
  2. При наличии группировки агрегатная функция МАКСИМУМ(ТоварыВДокументе.НомерСтроки) выбирает максимальный номер. Если в документе один и тот же товар указан несколько раз, соединение по номеру строки ТребуетсяТовара.НомерСтроки = ТоварыВДокументе.НомерСтроки может привести к некорректным результатам.
  3. В секции ГДЕ стоит фильтр ТоварыВДокументе.Ссылка = &Ссылка. Если таблица соединена через ЛЕВОЕ СОЕДИНЕНИЕ, наложение условия на поля правой таблицы в блоке ГДЕ превращает левое соединение во внутреннее, отсекая строки при отсутствии совпадений.

Причина 3. Отсутствие фиксации точки контроля в виртуальной таблице

В обращении к виртуальной таблице РегистрНакопления.ТоварныеЗапасы.Остатки(, ...) первый параметр оставлен пустым. Это означает, что остатки рассчитываются на текущий актуальный момент (на бесконечность), а не на момент времени проведения документа. Для корректной работы первым параметром виртуальной таблицы необходимо передавать &Период или &МоментВремени.

Оптимальный вариант запроса для контроля остатков

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


ВЫБРАТЬ
    ТоварыВДокументе.Товар КАК Товар,
    СУММА(ТоварыВДокументе.Количество) КАК Количество,
    МАКСИМУМ(ТоварыВДокументе.НомерСтроки) КАК НомерСтроки
ПОМЕСТИТЬ ТребуетсяТовара
ИЗ
    Документ.РасходТовара.Товары КАК ТоварыВДокументе
ГДЕ
    ТоварыВДокументе.Ссылка = &Ссылка
    И ТоварыВДокументе.Товар.Вид = ЗНАЧЕНИЕ(Перечисление.ВидыТоваров.Товар)
СГРУППИРОВАТЬ ПО
    ТоварыВДокументе.Товар
ИНДЕКСИРОВАТЬ ПО
    Товар
;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    ТребуетсяТовара.Товар КАК Товар,
    ПРЕДСТАВЛЕНИЕ(ТребуетсяТовара.Товар) КАК ТоварПредставление,
    -ТоварныеЗапасыОстатки.КоличествоОстаток КАК Нехватка,
    ТребуетсяТовара.Количество + ТоварныеЗапасыОстатки.КоличествоОстаток КАК ОстатокНаСкладе,
    ТребуетсяТовара.НомерСтроки КАК НомерСтроки
ИЗ
    ТребуетсяТовара КАК ТребуетсяТовара
        ВНУТРЕННЕЕ СОЕДИНЕНИЕ РегистрНакопления.ТоварныеЗапасы.Остатки(
                &МоментВремени,
                Товар В (ВЫБРАТЬ ТребуетсяТовара.Товар ИЗ ТребуетсяТовара)
                    И Склад = &Склад) КАК ТоварныеЗапасыОстатки
        ПО ТребуетсяТовара.Товар = ТоварныеЗапасыОстатки.Товар
ГДЕ
    ТоварныеЗапасыОстатки.КоличествоОстаток < 0
УПОРЯДОЧИТЬ ПО
    НомерСтроки

Разберем изменения, внесенные в запрос:

  1. Исключено лишнее левое соединение с физической табличной частью документа — все данные берутся напрямую из временной таблицы ТребуетсяТовара.
  2. Для соединения с регистром остатков используется ВНУТРЕННЕЕ СОЕДИНЕНИЕ, так как при контроле отрицательных остатков после записи движений нас интересуют только те позиции, по которым в регистре образовался отрицательный баланс.
  3. В первый параметр виртуальной таблицы остатков передан параметр &МоментВремени.
  4. В список выборки добавлено поле-ссылка ТребуетсяТовара.Товар наряду с ПРЕДСТАВЛЕНИЕ(ТребуетсяТовара.Товар), что позволяет при необходимости открывать форму объекта или анализировать реквизиты номенклатуры в программном коде.
← На главную