При реализации контроля остатков в процедуре проведения документов разработчики нередко сталкиваются с ситуацией, когда запрос проверки наличия товара не возвращает строк с нехваткой, и алгоритм не выдает пользователю положенные предупреждения. Разберем типичные ошибки в структуре запроса и логику работы механизма оперативного контроля остатков в платформе 1С:Предприятие 8.3.
Рассмотрим типовой запрос контроля остатков из документа РасходТовара, в котором создается временная таблица из табличной части, связывается с виртуальной таблицей остатков регистра накопления ТоварныеЗапасы и проверяется наличие свободных остатков на складе:
ВЫБРАТЬ
ТоварыВДокументе.Товар КАК Товар,
СУММА(ТоварыВДокументе.Количество) КАК Количество,
МАКСИМУМ(ТоварыВДокументе.НомерСтроки) КАК НомерСтроки
ПОМЕСТИТЬ ТребуетсяТовара
ИЗ
Документ.РасходТовара.Товары КАК ТоварыВДокументе
ГДЕ
ТоварыВДокументе.Ссылка = &Ссылка
И ТоварыВДокументе.Товар.Вид = ЗНАЧЕНИЕ(Перечисление.ВидыТоваров.Товар)
СГРУППИРОВАТЬ ПО
ТоварыВДокументе.Товар
ИНДЕКСИРОВАТЬ ПО
Товар
;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
ПРЕДСТАВЛЕНИЕ(ТребуетсяТовара.Товар) КАК ТоварПредставление,
ВЫБОР
КОГДА - ЕСТЬNULL(ТоварныеЗапасыОстатки.КоличествоОстаток, 0) > ТоварыВДокументе.Количество
ТОГДА ТоварыВДокументе.Количество
ИНАЧЕ - ЕСТЬNULL(ТоварныеЗапасыОстатки.КоличествоОстаток, 0)
КОНЕЦ КАК Нехватка,
ТребуетсяТовара.НомерСтроки КАК НомерСтроки
ИЗ
ТребуетсяТовара КАК ТребуетсяТовара
ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ТоварныеЗапасы.Остатки(
,
Товар В
(ВЫБРАТЬ
ТребуетсяТовара.Товар
ИЗ
ТребуетсяТовара)
И Склад = &Склад) КАК ТоварныеЗапасыОстатки
ПО ТребуетсяТовара.Товар = ТоварныеЗапасыОстатки.Товар
ЛЕВОЕ СОЕДИНЕНИЕ Документ.РасходТовара.Товары КАК ТоварыВДокументе
ПО ТребуетсяТовара.Товар = ТоварыВДокументе.Товар
И ТребуетсяТовара.НомерСтроки = ТоварыВДокументе.НомерСтроки
ГДЕ
ТоварыВДокументе.Ссылка = &Ссылка И
0 > ЕСТЬNULL(ТоварныеЗапасыОстатки.КоличествоОстаток, 0)
Проанализируем ключевые причины, по которым данный запрос может работать не так, как ожидалось.
Обратим внимание на условие в секции ГДЕ:
ГДЕ
0 > ЕСТЬNULL(ТоварныеЗапасыОстатки.КоличествоОстаток, 0)
Данное условие эквивалентно проверке КоличествоОстаток < 0. Это классический признак новой методики проведения. Выясним, как она устроена:
Движения.ТоварныеЗапасы.Записывать = Истина; Движения.ТоварныеЗапасы.Записать();.КоличествоОстаток < 0). Если такие строки найдены — проведение отменяется, а пользователю выводится сообщение об ошибке.Если же движения документа еще не были записаны перед выполнением запроса (применяется старая методика проведения), то отрицательный остаток в регистре просто не образуется. Соответственно, условие 0 > ... отсекает абсолютно все строки, и выборка оказывается пустой.
В тексте запроса присутствуют два соединения: первое — с виртуальной таблицей остатков, второе — повторное обращение к табличной части Документ.РасходТовара.Товары.
Посмотрим на ошибки, возникающие из-за повторного соединения:
ТребуетсяТовара. Повторное соединение с табличной частью Документ.РасходТовара.Товары является лишним и снижает производительность.МАКСИМУМ(ТоварыВДокументе.НомерСтроки) выбирает максимальный номер. Если в документе один и тот же товар указан несколько раз, соединение по номеру строки ТребуетсяТовара.НомерСтроки = ТоварыВДокументе.НомерСтроки может привести к некорректным результатам.ГДЕ стоит фильтр ТоварыВДокументе.Ссылка = &Ссылка. Если таблица соединена через ЛЕВОЕ СОЕДИНЕНИЕ, наложение условия на поля правой таблицы в блоке ГДЕ превращает левое соединение во внутреннее, отсекая строки при отсутствии совпадений.В обращении к виртуальной таблице РегистрНакопления.ТоварныеЗапасы.Остатки(, ...) первый параметр оставлен пустым. Это означает, что остатки рассчитываются на текущий актуальный момент (на бесконечность), а не на момент времени проведения документа. Для корректной работы первым параметром виртуальной таблицы необходимо передавать &Период или &МоментВремени.
Исправим запрос: уберем дублирующее соединение с табличной частью, используем поля временной таблицы и приведем логику к правильному виду новой методики проведения:
ВЫБРАТЬ
ТоварыВДокументе.Товар КАК Товар,
СУММА(ТоварыВДокументе.Количество) КАК Количество,
МАКСИМУМ(ТоварыВДокументе.НомерСтроки) КАК НомерСтроки
ПОМЕСТИТЬ ТребуетсяТовара
ИЗ
Документ.РасходТовара.Товары КАК ТоварыВДокументе
ГДЕ
ТоварыВДокументе.Ссылка = &Ссылка
И ТоварыВДокументе.Товар.Вид = ЗНАЧЕНИЕ(Перечисление.ВидыТоваров.Товар)
СГРУППИРОВАТЬ ПО
ТоварыВДокументе.Товар
ИНДЕКСИРОВАТЬ ПО
Товар
;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
ТребуетсяТовара.Товар КАК Товар,
ПРЕДСТАВЛЕНИЕ(ТребуетсяТовара.Товар) КАК ТоварПредставление,
-ТоварныеЗапасыОстатки.КоличествоОстаток КАК Нехватка,
ТребуетсяТовара.Количество + ТоварныеЗапасыОстатки.КоличествоОстаток КАК ОстатокНаСкладе,
ТребуетсяТовара.НомерСтроки КАК НомерСтроки
ИЗ
ТребуетсяТовара КАК ТребуетсяТовара
ВНУТРЕННЕЕ СОЕДИНЕНИЕ РегистрНакопления.ТоварныеЗапасы.Остатки(
&МоментВремени,
Товар В (ВЫБРАТЬ ТребуетсяТовара.Товар ИЗ ТребуетсяТовара)
И Склад = &Склад) КАК ТоварныеЗапасыОстатки
ПО ТребуетсяТовара.Товар = ТоварныеЗапасыОстатки.Товар
ГДЕ
ТоварныеЗапасыОстатки.КоличествоОстаток < 0
УПОРЯДОЧИТЬ ПО
НомерСтроки
Разберем изменения, внесенные в запрос:
ТребуетсяТовара.ВНУТРЕННЕЕ СОЕДИНЕНИЕ, так как при контроле отрицательных остатков после записи движений нас интересуют только те позиции, по которым в регистре образовался отрицательный баланс.&МоментВремени.ТребуетсяТовара.Товар наряду с ПРЕДСТАВЛЕНИЕ(ТребуетсяТовара.Товар), что позволяет при необходимости открывать форму объекта или анализировать реквизиты номенклатуры в программном коде.