Как запретить проведение документа при отрицательном или нулевом остатке товара?

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

В процессе разработки конфигураций на платформе 1С часто возникает задача ограничить списание товара, если его недостаточно на складе. Начинающие разработчики часто сталкиваются с ситуацией, когда код написан, запрос составлен, но документ всё равно проводится, игнорируя отсутствие товара. В этой статье мы подробно разберем, как правильно реализовать контроль остатков, исправим распространенные ошибки и изучим профессиональные методики проверки данных при проведении.

Анализ проблемы и поиск логической ошибки

Рассмотрим ситуацию, представленную в коде автора. Основная проблема заключается в блоке проверки результата запроса. Проанализируем фрагмент кода:


Если ВыборкаДетальныеЗаписи.Количество < 0 Тогда
    Пока ВыборкаДетальныеЗаписи.Следующий Цикл
        Сообщить ("Недостаточно товара" + ВыборкаДетальныеЗаписи.ТоварПредставление);
    КонецЦикла;
    Отказ = Истина;
КонецЕсли;

Здесь допущена критическая логическая ошибка: метод Количество() для выборки из результата запроса или таблицы значений всегда возвращает целое число от нуля и выше. Количество записей не может быть меньше нуля физически. Следовательно, условие Если ... < 0 никогда не выполняется, код внутри условия игнорируется, и переменная Отказ не принимает значение Истина. В результате документ успешно проводится даже при наличии "минусов".

Для исправления этой ошибки нам необходимо изменить условие. Мы должны проверять, есть ли в выборке хотя бы одна строка, свидетельствующая об отрицательном остатке. Правильнее всего использовать метод Пустой() самого результата запроса или проверять Количество() > 0.

Методика «Нового проведения» (Пост-контроль)

В коде автора используется современная методика контроля остатков, называемая «пост-контролем». Разберем ее суть по шагам:

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

Эта методика эффективнее «старой» (когда остатки проверяются до записи движений), так как она проще в написании и лучше работает в условиях многопользовательской среды.

Как реализовать запрет при «нулевом» остатке?

Автор вопроса также спрашивает, как запретить проведение при нулевом остатке. Важно понимать: если товара на складе вообще нет (ноль), то в виртуальной таблице Остатки для этого товара может просто не существовать записи. В таком случае условие ГДЕ КоличествоОстаток < 0 не сработает, так как строка не попадет в результат запроса.

Для решения этой задачи нам нужно изменить логику запроса. Мы должны взять товары из табличной части документа и соединить их с остатками в регистре с помощью ЛЕВОГО СОЕДИНЕНИЯ. Проанализируем, как это сделать:


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

Использование функции ЕСТЬNULL позволяет нам заменить отсутствие записи в регистре на число 0. Таким образом, условие <= 0 отловит и отрицательные значения, и ситуации, когда товара нет совсем.

Использование объекта «Граница» и Момента Времени

Для корректного получения остатков именно после движений текущего документа, необходимо правильно установить параметр &Граница. Если мы просто передадим Дата или МоментВремени(), запрос может не учесть движения самого документа, так как по умолчанию граница может быть исключающей.

Рассмотрим правильный способ установки параметра:


ГраницаКонтроля = Новый Граница(МоментВремени(), ВидГраницы.Включая);
Запрос.УстановитьПараметр("Граница", ГраницаКонтроля);

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

Группировка табличной части

Часто в документах один и тот же товар может встречаться в нескольких строках (например, с разной ценой или характеристиками). Если проверять остатки по каждой строке отдельно, можно допустить ошибку. Проанализируем ситуацию: на складе 5 шт. товара, в документе две строки по 3 шт. Каждая строка по отдельности меньше 5, но в сумме их 6. Чтобы избежать "перерасхода", перед проверкой остатков данные табличной части документа во временной таблице обязательно нужно сгруппировать по товару.

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

Теперь соберем все рекомендации воедино и представим эталонный вариант процедуры ОбработкаПроведения для управления остатками:


Процедура ОбработкаПроведения(Отказ, Режим)
    
    // 1. Формируем движения
    Движения.ТоварыНаСкладах.Записывать = Истина;
    Для Каждого ТекСтрока Из Товары Цикл
        Движение = Движения.ТоварыНаСкладах.ДобавитьРасход();
        Движение.Период = Дата;
        Движение.Товар = ТекСтрока.Товар;
        Движение.Склад = Склад;
        Движение.Количество = ТекСтрока.Количество;
    КонецЦикла;
    
    // 2. Записываем движения в базу (в транзакции)
    Движения.Записать();
    
    // 3. Подготавливаем запрос для проверки результата
    Запрос = Новый Запрос;
    Запрос.Текст = 
    "ВЫБРАТЬ
    |	ПродажаТовары.Товар КАК Товар,
    |	СУММА(ПродажаТовары.Количество) КАК КоличествоВДокументе
    |ПОМЕСТИТЬ ВТ_ТоварыДокумента
    |ИЗ
    |	Документ.ПродажаТовара.Товары КАК ПродажаТовары
    |ГДЕ
    |	ПродажаТовары.Ссылка = &Ссылка
    |ГРУППИРОВАТЬ ПО
    |	ПродажаТовары.Товар
    |;
    |
    |////////////////////////////////////////////////////////////////////////////////
    |ВЫБРАТЬ
    |	ВТ_ТоварыДокумента.Товар.Представление КАК ТоварПредставление,
    |	ТоварыНаСкладахОстатки.КоличествоОстаток КАК Остаток
    |ИЗ
    |	ВТ_ТоварыДокумента КАК ВТ_ТоварыДокумента
    |		ВНУТРЕННЕЕ СОЕДИНЕНИЕ РегистрНакопления.ТоварыНаСкладах.Остатки(&Граница, Склад = &Склад) КАК ТоварыНаСкладахОстатки
    |		ПО ВТ_ТоварыДокумента.Товар = ТоварыНаСкладахОстатки.Товар
    |ГДЕ
    |	ТоварыНаСкладахОстатки.КоличествоОстаток < 0";

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

Важность управляемых блокировок

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

Чтобы этого избежать, перед выполнением запроса необходимо устанавливать блокировку данных. Это заставит второй процесс подождать, пока первый не завершит свою транзакцию (проверку остатков и проведение). Для этого используется объект БлокировкаДанных, в который добавляются элементы блокировки по конкретному складу и списку товаров из документа.

Таким образом, качественный контроль остатков в 1С — это не просто сравнение чисел, а комплекс мер, включающий правильную работу с транзакциями, понимание границ периодов и грамотное использование механизмов платформы.

← На главную