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

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

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

Разбор типичных ошибок в коде автора

Рассмотрим исходный код, который был предложен в вопросе. Автор пытается выполнить проверку в процедуре ПриЗаписи, используя запрос к виртуальной таблице остатков с условием КоличествоОстаток < 0. Разберем, почему этот подход не работает:

  1. Момент записи движений. В 1С документ сначала записывает свои данные, а затем (при проведении) формирует движения по регистрам. Если вы выполняете запрос в процедуре ПриЗаписи, движения документа еще не зафиксированы в базе данных (или транзакция еще не завершена). Следовательно, запрос к остаткам не видит того «минуса», который должен создать текущий документ.
  2. Место расположения кода. Если процедура ПриЗаписи находится в модуле формы, она сработает только при интерактивном нажатии кнопки пользователем. При программном создании или изменении объекта этот код будет проигнорирован. Контроль остатков всегда должен располагаться в модуле объекта.
  3. Логика проверки «на минус». Условие ГДЕ КоличествоОстаток < 0 в запросе эффективно только в так называемой «новой методике» контроля, когда мы сначала записываем движения, а потом проверяем результат. Если движения еще не записаны, остаток в регистре всё еще положительный, и запрос вернет пустой результат.

Методика №1: Классический контроль (до записи движений)

Рассмотрим первый способ, который часто называют «старой методикой». Суть заключается в том, чтобы перед совершением движений проверить, достаточно ли товара на складе. Эту проверку следует выполнять в процедуре ОбработкаПроведения модуля объекта.

Проанализируем шаги реализации:

  1. Блокируем данные, чтобы другие пользователи не могли изменить остатки по нашей номенклатуре в момент проверки.
  2. Выгружаем товары из табличной части в запрос.
  3. Получаем текущие остатки из регистра.
  4. Сравниваем требуемое количество с имеющимся.

Пример реализации классической методики:


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

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

    Запрос.УстановитьПараметр("ТЧ_Товары", Товары.Выгрузить());
    Запрос.УстановитьПараметр("МоментВремени", МоментВремени());

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

    // Если нехватка не обнаружена, записываем движения
    Если НЕ Отказ Тогда
        // Здесь код формирования движений
    КонецЕсли;

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

Методика №2: Современный контроль (после записи движений)

Теперь разберем «новую методику». Она считается более производительной в высоконагруженных системах. Рассмотрим её алгоритм:

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

Важно помнить: при использовании этой методики в запросе необходимо проверять остатки именно на МоментВремени() документа, включая его движения. Посмотрим на пример кода:


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

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

Важные технические нюансы

Выясним причину, почему даже при использовании ОбработкаПроведения проверка может не срабатывать. Проанализируем дополнительные факторы:

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

Управляемые блокировки. Проанализируем ситуацию: два пользователя одновременно проводят документ на 10 единиц товара, а на остатке всего 15. Если не использовать объект БлокировкаДанных, оба запроса на проверку остатков вернут «15», оба документа решат, что товара хватает, и проведутся. В итоге остаток станет «-5». Использование исключительной блокировки заставляет второго пользователя ждать, пока первый завершит свою транзакцию.

Свойство Отказ. Установка Отказ = Истина внутри транзакции проведения (которая автоматически открывается при вызове ОбработкаПроведения) приводит к тому, что система автоматически отменяет все внесенные изменения и откатывает транзакцию. Данные в регистрах и сам статус документа не изменятся.

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

← На главную