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