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