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