Как получить актуальные остатки по регистру накопления в обработке проведения документа

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

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

Причина: поведение наборов записей и транзакций в 1С

По умолчанию платформа 1С работает по следующему принципу: при заполнении коллекции Движения строки добавляются только в оперативную память. Физическая запись набора записей в базу данных происходит автоматически в самом конце процедуры ОбработкаПроведения (при успешном завершении транзакции).

Если внутри процедуры проведения выполняется запрос к виртуальной таблице РегистрНакопления.ИмяРегистра.Остатки, СУБД считывает только те записи, которые уже физически зафиксированы в таблицах базы данных. Движения текущего документа, находящиеся в памяти, в выборку не попадают.

Решение 1: Явная запись движений (Новая методика проведения)

Чтобы движения текущего документа стали доступны для запросов внутри текущей транзакции, их необходимо принудительно записать в базу данных с помощью метода Записать() нужного набора записей. Рассмотрим пошаговый алгоритм так называемой «новой методики проведения» (контроль остатков после записи движений):

  1. Очищаем и записываем старые движения документа (если документ перепроводится), чтобы исключить задвоение.
  2. Формируем новые движения расхода или прихода в коллекции Движения.
  3. Явно вызываем метод Записать() для регистра.
  4. Выполняем запрос к виртуальной таблице остатков на момент времени документа с отбором по отрицательным остаткам.
  5. Если обнаружена нехватка товара (отрицательный остаток), устанавливаем флаг Отказ = Истина и отменяем транзакцию.

Посмотрим на пример реализации формирования движений и последующего контроля:


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

Важные нюансы работы с границей периода

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

ГраницаОстатков = Новый Граница(МоментВремени(), ВидГраницы.Включая);

Решение 2: Классическая методика (Контроль и подбор до формирования движений)

Если требуется списать партии по методу FIFO или LIFO, движения формируются на основе уже имеющихся остатков. В этом случае алгоритм строится иначе:

  1. Очищаем и записываем старые движения перепроводимого документа: Движения.ИмяРегистра.Записать().
  2. Устанавливаем управляемую блокировку данных на измерения регистра с помощью объекта БлокировкаДанных, чтобы исключить конкурентный доступ других пользователей.
  3. Запрашиваем доступные остатки партий на МоментВремени() документа (без учета текущего документа).
  4. В кодовой логике распределяем остатки по строкам документа, рассчитываем себестоимость и формируем движения.
  5. Флаг Движения.ИмяРегистра.Записывать = Истина обеспечивает сохранение сформированных строк при фиксации транзакции.

Архитектурная ошибка: перепроведение будущих документов внутри «ОбработкаПроведения»

Проанализируем распространенную ошибку: попытку перепроводить последующие документы задним числом прямо из обработчика текущего документа. Делать так категорически не рекомендуется по следующим причинам:

  1. Взаимоблокировки (Deadlocks): каскадное проведение документов захватывает большое количество таблиц и строк в рамках одной транзакции, что блокирует параллельную работу всех пользователей в системе.
  2. Переполнение стека и транзакционные сбои: при большой цепочке документов транзакция может превысить тайм-аут ожидания СУБД или исчерпать ресурсы сервера.

Для реализации корректного партионного учета задним числом в платформе 1С применяются стандартные механизмы:

  1. Последовательности документов: использование объекта конфигурации Последовательность. При проведении документа задним числом граница последовательности автоматически сдвигается, а актуализация партий выполняется регламентным заданием или через обработку «Восстановление последовательности».
  2. Двухфазное проведение: при оперативном проведении фиксируется только факт списания количества, а детальный партионный учет и расчет себестоимости рассчитываются регламентной процедурой закрытия месяца (как это реализовано в типовых конфигурациях 1С:Бухгалтерия и 1С:ERP).
← На главную