Как правильно реализовать проверку корректного числа товаров на складе при проведении документов в 1С?

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

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

Почему нельзя обращаться к остаткам через «точку»

Типичная ошибка начинающего разработчика — попытка получить остаток напрямую через объект метаданных, например: РегистрНакопления.ТоварыНаСкладах.Остатки.... Проанализируем, почему это не работает. Платформа 1С хранит данные в таблицах базы данных, а регистр накопления — это сложная структура, состоящая из таблицы движений и таблиц итогов. Чтобы получить актуальное число товара на конкретном складе, необходимо выполнить расчет. Единственный правильный и эффективный способ получить эти данные в коде — это использование Запроса к виртуальной таблице Остатки.

Основные методики контроля остатков

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

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

Реализация контроля через запрос (классический подход)

Рассмотрим пошагово, как внедрить проверку в процедуру ОбработкаПроведения модуля документа. Нам необходимо выполнить запрос к виртуальной таблице ТоварыНаСкладах.Остатки. Выясним, какие параметры важны для этого запроса.

Во-первых, запрос должен учитывать конкретный склад и конкретный список товаров из табличной части документа. Во-вторых, мы должны получать остатки на МоментВремени нашего документа.

Пример кода для реализации такой проверки:


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

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

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

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

    Если Отказ Тогда
        Возврат;
    КонецЕсли;

    // 3. Если проверка пройдена — формируем движения
    // ... здесь идет стандартный код формирования записей в Движения.ТоварыНаСкладах ...

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

Разберем важные детали реализации

Группировка табличной части: Обратите внимание, что в запросе мы сначала помещаем данные документа во временную таблицу и группируем их по товару. Это необходимо на случай, если пользователь ввел один и тот же товар в две разные строки документа. Без группировки проверка будет работать некорректно.

Использование Момента Времени: Мы передаем МоментВремени() в параметры виртуальной таблицы остатков. Это позволяет получить остатки именно на ту секунду и ту позицию в последовательности документов, которую занимает наш текущий документ.

Блокировка данных: Для того чтобы гарантировать отсутствие изменений остатков другими пользователями в процессе нашей проверки, необходимо использовать объект БлокировкаДанных. Это критически важно для профессиональной разработки. Блокировка устанавливается по измерениям «Склад» и «Товар» перед выполнением запроса.


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

Оперативное и неоперативное проведение

Проанализируем ситуацию: всегда ли нужен такой жесткий контроль? В 1С существует понятие оперативного проведения (проведение «сейчас»). Обычно контроль остатков включают только для этого режима. Если бухгалтер исправляет документ прошлым числом (неоперативно), контроль может мешать, так как текущие остатки уже изменились. Выясним, как это реализовать: достаточно добавить условие Если РежимПроведения = РежимПроведенияДокумента.Оперативный Тогда... вокруг блока проверки остатков.

Информативность для пользователя

Просто прервать проведение документа недостаточно. Пользователь должен понимать, почему это произошло. Хорошим тоном считается выводить не просто сообщение «Товара нет», а детальный расчет дефицита. В нашем примере кода мы использовали СообщениеПользователю, которое выводит конкретное название товара и цифры: сколько запрашивается и сколько реально есть на складе. Это экономит время кладовщика или менеджера на поиск ошибки.

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

← На главную