Почему запрос не выбирает цену при повторном подборе номенклатуры в сохраненном документе 1С?

Программист 1С v8.3 (Обычные формы) Управленческий учет Промышленность, строительство и АПК
← На главную

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

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

Рассмотрим стандартный сценарий. Программист пишет запрос к регистру накопления (например, ПартииТМЦРегламентные), чтобы получить ЦенаИзготовителя. Пока документ новый, его дата в памяти часто соответствует началу дня или текущему моменту, но он еще не зафиксирован в базе данных. Как только нажимается кнопка «Записать», документ получает точную позицию на оси времени. Если мы пытаемся выполнить запрос на ту же самую Дата, что и у документа, возникает неопределенность: должны ли движения текущего документа попасть в результат запроса?

Проанализируем ситуацию, описанную пользователем. При подборе номенклатуры в новом документе цена подставляется. Но при повторном подборе в уже записанном документе запрос возвращает пустой результат. Выясним причину этого поведения и разберем по шагам, как исправить код.

Ошибка 1: Использование условий в секции ГДЕ вместо параметров

В представленном коде запрос обращается к виртуальной таблице Остатки, но фильтры по номенклатуре и складу накладываются в блоке ГДЕ. Посмотрим на пример кода из сообщения:


ИЗ
    РегистрНакопления.ПартииТМЦРегламентные.Остатки КАК ПартииТМЦРегламентныеОстатки
ГДЕ
    ПартииТМЦРегламентныеОстатки.МестоХранения = &МестоХранения
    И ПартииТМЦРегламентныеОстатки.Номенклатура = &Номенклатура

Разберем, почему это плохо. Когда мы используем ГДЕ для виртуальной таблицы остатков, система сначала вычисляет остатки по всем записям регистра на указанную дату, а уже потом фильтрует этот огромный массив данных по нашей номенклатуре. Это не только замедляет работу, но и приводит к ошибкам получения данных, если документ уже записан. Правильный подход — передавать условия в параметры самой виртуальной таблицы.

Ошибка 2: Работа с датами и «разыменование» ссылок

Проанализируем условие НАЧАЛОПЕРИОДА(ПартииТМЦРегламентныеОстатки.ДокументПоступления.Дата, ДЕНЬ) = &Дата. Здесь допущены сразу две серьезные ошибки:

  1. Обращение через точку в запросе: Обращение к ДокументПоступления.Дата заставляет 1С выполнять неявное соединение (LEFT JOIN) с таблицей документа поступления. Если в регистре тысячи записей, запрос станет крайне медленным.
  2. Сравнение дат: Сравнение дат на равенство в запросах — это всегда риск. Разница в одну секунду или даже миллисекунду (в SQL) приведет к тому, что условие не выполнится.

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

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

Рассмотрим подробнее, как должен выглядеть правильно инициализированный параметр периода:


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

Если же мы работаем в форме нового документа, который еще не записан, можно использовать текущую дату. Однако универсальным способом является получение МоментВремени().

Правильная структура запроса

Перепишем исходный запрос, внедрив все рекомендации. Мы переместим условия в параметры виртуальной таблицы и оптимизируем выборку полей.


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

Почему это решение работает?

Разберем по шагам, что изменилось в нашем подходе:

  1. Скорость: Параметр &Условие в виртуальной таблице позволяет платформе 1С подготовить эффективный SQL-запрос, который выберет только нужные записи из итогов регистра.
  2. Точность: Объект Граница с типом Исключая гарантирует, что если вы перевыбираете номенклатуру в уже проведенном документе, его собственные записи в регистре не «испортят» картину остатков. Вы получите состояние склада ровно за миг до этого документа.
  3. Надежность: Мы ушли от сравнения дат через НАЧАЛОПЕРИОДА. Вместо того чтобы искать документы поступления за конкретный день, мы полагаемся на актуальные остатки в разрезе партий (измерение ДокументПоступления в регистре накопления само по себе является фильтром).

Рекомендация по отладке

Если даже после применения Граница данные не выбираются, воспользуйтесь советом из обсуждения: установите точку останова и проверьте значения параметров. Часто оказывается, что МестоХранения или Номенклатура в момент выполнения кода пусты или содержат не те данные, которые ожидает программист.

Также помните, что сообщение «Ошибка чтения значения» в отладчике для полей Итог.ЦенаИзготовителя часто означает просто то, что Выборка.Следующий() вернул Ложь (то есть запрос пуст). В консоли запросов всегда проверяйте результат с теми же параметрами Период и Ссылка, которые используются в коде.

Проанализировав ситуацию, мы выяснили, что ключ к решению проблемы кроется в понимании механизмов работы виртуальных таблиц. Использование МоментВремени и правильная передача параметров — это фундамент стабильной работы любых механизмов подбора в 1С.

← На главную