Как получить остатки в 1С до проведения документа и избежать учета текущих движений

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

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

Проблема: почему запрос видит движения текущего документа?

Проанализируем типичную ситуацию. Программист пишет запрос к регистру накопления, используя в качестве параметра даты МоментВремени документа или Границу с видом Исключая. Однако при перепроведении документа запрос внезапно возвращает остатки уже с учетом «расхода», который сделал этот же документ в прошлом сеансе проведения. Выясним причину этого поведения.

Когда мы запускаем проведение уже существующего документа (неоперативное проведение или перепроведение), в базе данных уже могут существовать его старые движения. Если в свойствах документа на вкладке «Движения» не установлено Удалять автоматически, то старые записи регистра остаются в таблицах до тех пор, пока платформа или программист их не перезапишут. Даже при использовании ВидГраницы.Исключая, виртуальная таблица 1С может учитывать эти записи, так как они имеют тот же МоментВремени, что и сам документ.

Решение 1: Использование объекта Граница и Момента времени

Разберем первый и самый очевидный способ — правильную установку периода в параметрах виртуальной таблицы. Рассмотрим пример кода, который часто используется разработчиками:


ДатаПроверки = Новый Граница(ЭтотОбъект.МоментВремени(), ВидГраницы.Исключая);
Запрос.УстановитьПараметр("ДатаПроверки", ДатаПроверки);

Важно понимать разницу между Дата и МоментВремени. Дата включает только время до секунд. МоментВремени — это комбинация даты и ссылки на документ. Это позволяет системе однозначно определить положение документа на временной оси, даже если в одну и ту же секунду записано несколько документов. Использование ВидГраницы.Исключая говорит системе: «Дай мне итоги по состоянию ПЕРЕД этим моментом времени».

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

Решение 2: Принудительная очистка движений перед запросом

Это самое надежное решение проблемы «призрачных» остатков. Рассмотрим подробнее механизм транзакции. Когда начинается проведение, открывается транзакция. Чтобы запрос гарантированно не увидел старые движения этого же документа, мы должны явно «обнулить» их в базе данных. Посмотрим на пример кода, который необходимо вставить в процедуру ОбработкаПроведения перед выполнением запроса:


// Очищаем движения текущего документа в базе данных
Движения.Запасы.Записывать = Истина;
Движения.Запасы.Записать();

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

// Используем МоментВремени текущего документа
Запрос.УстановитьПараметр("ДатаГраница", Новый Граница(ЭтотОбъект.МоментВремени(), ВидГраницы.Исключая));

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

Решение 3: Проверка настроек метаданных (Удаление движений)

Проанализируем настройки самого объекта Документ в конфигураторе. На вкладке «Движения» есть важное свойство Удаление движений. Оно может принимать три значения:

  1. Удалять автоматически при перепроведении: Платформа сама очищает движения перед вызовом ОбработкаПроведения. В этом случае Решение 2 не требуется, но это может замедлять работу системы при массовом перепроведении.
  2. Не удалять автоматически: Движения остаются в базе. Программист обязан сам управлять их очисткой или перезаписью. Именно в этом режиме чаще всего возникают проблемы с получением остатков «ДО» документа.
  3. Записывать выбранные: Платформа удаляет движения только тех регистров, которые будут явно изменены.

Если в вашей конфигурации установлено Не удалять автоматически, использование метода Движения.ИмяРегистра.Записать() перед запросом является обязательным стандартом разработки.

Решение 4: «Новая методика» проведения

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

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

Этот метод исключает необходимость использования ВидГраницы.Исключая и манипуляций с очисткой движений, так как мы анализируем финальный результат проведения. Это также помогает избежать избыточных блокировок таблиц итогов.

Резюме и рекомендации

Выясним, какой способ выбрать. Если вы работаете в рамках «старой методики» (контроль остатков перед записью):

Соблюдение этих правил позволит вам всегда получать актуальные данные и избежать трудноуловимых ошибок при расчете себестоимости и контроле складских запасов.

← На главную