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

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

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

Анализ проблемы в исходном коде

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

Главная ошибка, на которую указали участники форума, кроется в проверке результата запроса:


Если ВыборкаДетальныеЗаписи.Количество() < 0 Тогда      
    // ... код вывода сообщения
    Отказ = Истина;
КонецЕсли;

Метод Количество() возвращает число строк в выборке. Это число не может быть меньше нуля. Следовательно, условие < 0 никогда не выполняется, код внутри условия игнорируется, и документ проводится даже при критическом дефиците товара. Кроме того, если автор хочет запретить проведение и при нулевом остатке, условие в самом запросе также требует корректировки.

Корректная методика контроля остатков (Новая методика)

Рассмотрим современный и надежный способ реализации контроля. Суть его заключается в том, что мы позволяем документу сделать движения, записываем их в базу данных в рамках транзакции проведения, а затем проверяем, не стали ли остатки после этого «плохими» (отрицательными). Если стали — мы отменяем транзакцию, устанавливая Отказ = Истина.

Шаг 1. Формирование и запись движений

В начале процедуры ОбработкаПроведения мы подготавливаем наборы записей. Важно установить флаг Записывать = Истина для тех регистров, по которым будет проводиться контроль. Мы принудительно записываем движения, чтобы запрос увидел актуальную картину с учетом текущего документа.


Процедура ОбработкаПроведения(Отказ, Режим)
    
    // Очищаем движения, если документ перепроводится
    Движения.ТоварыНаСкладах.Очистить();
    
    // Указываем, что регистр нужно записать
    Движения.ТоварыНаСкладах.Записывать = Истина;
    
    Для Каждого ТекСтрокаТовары Из Товары Цикл
        Движение = Движения.ТоварыНаСкладах.ДобавитьРасход();
        Движение.Период = Дата;
        Движение.Товар = ТекСтрокаТовары.Товар;
        Движение.Склад = Склад;
        Движение.Количество = ТекСтрокаТовары.Количество;
    КонецЦикла;
    
    // Записываем движения в базу данных, чтобы запрос их увидел
    Движения.ТоварыНаСкладах.Записать();
    
    // Переходим к проверке...

Шаг 2. Подготовка запроса для контроля

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

Важный момент: если пользователь хочет запретить проведение при «нулевом остатке» (то есть нельзя продать товар, если его в итоге останется 0), условие в запросе должно выглядеть как КоличествоОстаток <= 0. Однако обычно под «запретом при нулевом остатке» подразумевают запрет продажи того, чего нет вообще. В этом случае мы ищем КоличествоОстаток < 0 после списания.


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

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

РезультатЗапроса = Запрос.Выполнить();

Шаг 3. Обработка результатов и отмена проведения

Теперь выясним, как правильно обработать результат. Вместо проверки Количество() < 0, мы должны проверить, не пуста ли выборка. Если в выборку попали записи, значит, есть товары с отрицательным остатком.


Если НЕ РезультатЗапроса.Пустой() Тогда
    
    Выборка = РезультатЗапроса.Выбрать();
    
    Пока Выборка.Следующий() Цикл
        // Формируем сообщение пользователю
        Сообщение = Новый СообщениеПользователю;
        Сообщение.Текст = "Недостаточно товара " + Выборка.ТоварПредставление + 
                          ". Текущий дефицит: " + (-Выборка.КоличествоОстаток) + " шт.";
        Сообщение.Сообщить();
        
        // Устанавливаем флаг отказа в проведении
        Отказ = Истина;
    КонецЦикла;
    
КонецЕсли;

КонецПроцедуры

Важные рекомендации и нюансы

  1. Использование управляемых блокировок: В многопользовательской среде крайне важно использовать объект БлокировкаДанных перед выполнением запроса. Это гарантирует, что пока вы проверяете остаток, другой пользователь не успеет «перехватить» этот товар своим документом.
  2. Группировка в запросе: Всегда группируйте товары из табличной части документа. Если товар «Болт» указан в двух строках по 5 штук, а на складе всего 7, то без группировки проверка каждой строки в отдельности может не показать ошибку, хотя суммарно возникнет минус.
  3. Граница момента времени: Обратите внимание на использование Новый Граница(МоментВремени(), ВидГраницы.Включая). Если использовать просто МоментВремени(), виртуальная таблица остатков может не учесть движения самого проводимого документа, так как по умолчанию момент времени — это «миг до документа».
  4. Обработка нулевых остатков: Если ваша бизнес-логика подразумевает, что на складе всегда должен оставаться хотя бы один экземпляр (нельзя продавать «в ноль»), измените условие в запросе на ТоварыНаСкладахОстатки.КоличествоОстаток <= 0. В этом случае документ не проведется, если после продажи остаток станет равным нулю.

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

← На главную