Как правильно контролировать остатки товаров в регистре накопления 1С?

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

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

Проблема автора и её причины

Изначальная задумка автора заключалась в использовании процедуры ПриЗаписи модуля набора записей регистра накопления ТоварыНаСкладах. В этой процедуре он пытался выполнить запрос к виртуальной таблице остатков, чтобы проверить, не образовались ли отрицательные остатки для номенклатуры и склада, для которых установлен флаг "Контролировать остаток" в соответствующих справочниках. Вот фрагмент его запроса:


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

Затем он устанавливал параметры запроса и обрабатывал результат:


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

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

Современный подход к контролю остатков в 1С (новая методика)

В современных конфигурациях 1С (таких как 1С:Управление торговлей 11, 1С:ERP Управление предприятием) и при сдаче экзаменов на специалиста 1С активно применяется так называемая "новая" методика контроля остатков. Её суть заключается в следующем:

  1. Сначала записываем движения: Все движения документа (списание, приход, перемещение) сначала полностью записываются в регистры накопления. Это происходит внутри текущей транзакции проведения документа.
  2. Затем проверяем остатки: После того как все движения документа записаны, выполняется запрос к регистру накопления для получения актуальных остатков. Благодаря тому, что запрос выполняется внутри той же транзакции, он видит состояние остатков с учетом только что записанных движений.
  3. Отмена транзакции при минусе: Если в результате проверки обнаруживаются отрицательные остатки по контролируемым позициям, это означает, что товара на складе недостаточно. В этом случае транзакция проведения документа отменяется (происходит откат), и пользователю выдается соответствующее сообщение об ошибке.

Такой подход имеет несколько важных преимуществ:

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

Пошаговая реализация контроля остатков

Давайте разберем по шагам, как можно реализовать контроль остатков, используя "новую" методику, в процедуре ОбработкаПроведения модуля объекта документа.

Шаг 1: Очистка и запись движений документа

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


// В модуле объекта документа, в процедуре ОбработкаПроведения(Отказ, РежимПроведения)

// 1. Очищаем старые движения регистра "ТоварыНаСкладах" для текущего документа.
// Это критично при перепроведении документа, чтобы старые движения не влияли на контроль.
Движения.ТоварыНаСкладе.Очистить();

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

// 3. Записываем сформированные движения.
// Важно: на этом этапе движения физически записываются в регистр,
// и с этого момента они становятся доступными для запросов к виртуальным таблицам остатков
// в рамках текущей транзакции проведения документа.
Движения.ТоварыНаСкладе.Записать();

Шаг 2: Формирование запроса для проверки остатков

Теперь, когда движения записаны, мы можем выполнить запрос, чтобы проверить остатки. Мы используем виртуальную таблицу Остатки и фильтруем её по тем позициям, которые были затронуты текущим документом. Также учтем реквизиты справочников КонтролироватьОстаток.


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

// Устанавливаем параметр &Регистратор, который является ссылкой на текущий документ.
// Это позволяет выбрать только те Номенклатуру и Склад,
// которые участвовали в движениях текущего регистратора.
Запрос.УстановитьПараметр("Регистратор", Ссылка); // Ссылка на текущий документ

Давайте подробнее проанализируем этот запрос:

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

Шаг 3: Выполнение запроса и обработка результата

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


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

// Проверяем, вернул ли запрос какие-либо записи (есть ли отрицательные остатки)
Если Не РезультатЗапроса.Пустой() Тогда
    // Если результат не пустой, значит, есть отрицательные остатки.
    // Устанавливаем Отказ в Истина, чтобы отменить проведение документа.
    Отказ = Истина;

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

Присвоение переменной Отказ значения Истина в процедуре ОбработкаПроведения приводит к отмене транзакции, и все изменения, включая записанные движения, откатываются. Таким образом, регистр накопления остается в том состоянии, в котором был до попытки проведения документа.

Дополнительные нюансы и рекомендации

  1. Производительность: Обсуждение в форуме затрагивало вопрос производительности. Важно помнить, что каждый запрос, особенно в цикле или в процедуре, которая часто выполняется (как проведение документов), может влиять на общую производительность системы. Поэтому старайтесь оптимизировать запросы, использовать индексы и фильтровать данные как можно раньше. Предложенный подход с подзапросом для отбора только затронутых позиций уже является шагом к оптимизации.
  2. Контроль по разным измерениям: В нашем примере контроль ведется по Номенклатуре и Складу. Если у вас есть другие измерения в регистре накопления (например, Характеристика, Серия), их также необходимо включать в подзапрос и в выборку остатков для более точного контроля.
  3. Блокирующее чтение: Для обеспечения максимальной целостности данных в условиях высокой конкуренции (много пользователей, одновременно списывающих один и тот же товар) иногда используется блокирующее чтение. Однако в случае с "новой" методикой, когда весь процесс происходит в рамках одной транзакции, система управления базами данных (СУБД) обычно сама обеспечивает необходимые блокировки. Если транзакция не фиксируется из-за отрицательных остатков, все её изменения откатываются, предотвращая нарушения.
  4. Настройки контроля в типовых конфигурациях: Многие типовые конфигурации 1С уже имеют встроенные механизмы контроля отрицательных остатков, часто с возможностью настройки по организациям, складам или даже видам номенклатуры. Всегда рекомендуется сначала изучить стандартные возможности конфигурации, прежде чем реализовывать собственные.

Таким образом, для эффективного и корректного контроля остатков в 1С:Предприятие рекомендуется использовать "новую" методику, реализуя её в модуле объекта документа, в процедуре ОбработкаПроведения. Этот подход обеспечивает надежность, гибкость и корректную работу даже в сложных сценариях перепроведения документов.

← На главную