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