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